<!DOCTYPE html>
<html>
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
  <meta name="description" content="tong.li&#39;s blog">
  <meta name="keyword" content="彤哥哥博客，95后技术爱好者,现就职于同程旅行/同程艺龙上海分公司，专注于互联网技术分享的平台。">
  
    <link rel="shortcut icon" href="/css/images/icon.png">
  
  <title>
    
      Netty专题-初识NIO | 彤哥哥的博客
    
  </title>
  <link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <link href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
  <link href="https://cdn.staticfile.org/highlight.js/9.12.0/styles/tomorrow-night.min.css" rel="stylesheet">
  
<link rel="stylesheet" href="/css/style.css">

  
  <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://cdn.staticfile.org/geopattern/1.2.3/js/geopattern.min.js"></script>
  <script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
  
    
<script src="/js/qrious.js"></script>

  
  
  
  
    <!-- MathJax support START -->
    <script type="text/x-mathjax-config">
      MathJax.Hub.Config({
        tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
        }
      });
    </script>

    <script type="text/x-mathjax-config">
      MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax(), i;
        for (i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
        }
      });
    </script>
    <script type="text/javascript" src="https://cdn.staticfile.org/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
    <!-- MathJax support END -->
  


  
  
    
<script src="/js/local-search.js"></script>


<meta name="generator" content="Hexo 5.4.2"></head>
<div class="wechat-share">
  <img src="/css/images/logo.png" />
</div>
  <body>
    <header class="header fixed-header">
  <div class="header-container">
    <a class="home-link" href="/">
      <div class="logo"></div>
      <span>彤哥哥的博客</span>
    </a>
    <ul class="right-list">
      
        <li class="list-item">
          
            <a href="/" class="item-link">主页</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/series/" class="item-link">分类</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/tags/" class="item-link">标签</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/archives/" class="item-link">归档</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/project/" class="item-link">项目</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/about/" class="item-link">关于</a>
          
        </li>
      
      
        <li class="menu-item menu-item-search right-list">
    <a role="button" class="popup-trigger">
        <i class="fa fa-search fa-fw"></i>
    </a>
</li>
      
    </ul>
    <div class="menu">
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </div>
    <div class="menu-mask">
      <ul class="menu-list">
        
          <li class="menu-item">
            
              <a href="/" class="menu-link">主页</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/series/" class="menu-link">分类</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/tags/" class="menu-link">标签</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/archives/" class="menu-link">归档</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/project/" class="menu-link">项目</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/about/" class="menu-link">关于</a>
            
          </li>
        
      </ul>
    </div>
    
      <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
            <span class="search-icon">
                <i class="fa fa-search"></i>
            </span>
            <div class="search-input-container">
                <input autocomplete="off" autocapitalize="off"
                    placeholder="Please enter your keyword(s) to search." spellcheck="false"
                    type="search" class="search-input">
            </div>
            <span class="popup-btn-close">
                <i class="fa fa-times-circle"></i>
            </span>
        </div>
        <div id="search-result">
            <div id="no-result">
                <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
            </div>
        </div>
    </div>
</div>
    
  </div>
</header>

    <div id="article-banner">
  <h2>Netty专题-初识NIO</h2>
  <p class="post-date">2021-05-24</p>
  <div class="arrow-down">
    <a href="javascript:;"></a>
  </div>
</div>
<main class="app-body flex-box">
  <!-- Article START -->
  <article class="post-article">
    <section class="markdown-content"><p>Non-Blocking IO 非阻塞 IO，JDK1.4 引入的NIO指的是New IO。</p>
<h1 id="1-三大组件"><a href="#1-三大组件" class="headerlink" title="1. 三大组件"></a>1. 三大组件</h1><h2 id="1-1-Channel-amp-Buffer"><a href="#1-1-Channel-amp-Buffer" class="headerlink" title="1.1 Channel &amp; Buffer"></a>1.1 Channel &amp; Buffer</h2><p>channel 有一点类似于 stream，它就是读写数据的<strong>双向通道</strong>，可以从 channel 将数据读入 buffer，也可以将 buffer 的数据写入 channel，而之前的 stream 要么是输入，要么是输出，channel 比 stream 更为底层</p>
<pre class="mermaid">graph LR
channel --> buffer
buffer --> channel</pre>

<p>常见的 Channel 有</p>
<ul>
<li>FileChannel: 用于读取、写入、映射和操作文件的通道。</li>
<li>DatagramChannel: 通过UDP读写网络中的数据通道。</li>
<li>SocketChannel: 通过TCP读写网络中的数据。</li>
<li>ServerSocketChannel: 可以监听新进来的TCP连接，对每一个连接都创建一个SocketChannel。</li>
</ul>
<p>buffer 则用来缓冲读写数据，常见的 buffer 有</p>
<ul>
<li>ByteBuffer<ul>
<li>MappedByteBuffer：内存映射Buffer，映射到物理内存。</li>
<li>DirectByteBuffer：    直接内存Buffer，直接向操作系统申请，申请速度较慢。</li>
<li>HeapByteBuffer：     堆内存Buffer,受JVM GC的管理，申请快。</li>
<li>HeapByteBufferR：   只读堆内存Buffer</li>
</ul>
</li>
<li>ShortBuffer</li>
<li>IntBuffer</li>
<li>LongBuffer</li>
<li>FloatBuffer</li>
<li>DoubleBuffer</li>
<li>CharBuffer</li>
</ul>
<h2 id="1-2-Selector"><a href="#1-2-Selector" class="headerlink" title="1.2 Selector"></a>1.2 Selector</h2><p>selector 单从字面意思不好理解，需要结合服务器的设计演化来理解它的用途</p>
<h3 id="多线程版设计"><a href="#多线程版设计" class="headerlink" title="多线程版设计"></a>多线程版设计</h3><pre class="mermaid">graph TD
subgraph 多线程版
t1(thread) --> s1(socket1)
t2(thread) --> s2(socket2)
t3(thread) --> s3(socket3)
end</pre>
<h4 id="⚠️-多线程版缺点"><a href="#⚠️-多线程版缺点" class="headerlink" title="⚠️ 多线程版缺点"></a>⚠️ 多线程版缺点</h4><ul>
<li>内存占用高，每个线程都会占用一块内存(<a target="_blank" rel="noopener" href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BABHDABI">-Xss</a>,默认值：64位机器占1M，32位占320KB)</li>
<li>线程上下文切换成本高，用户态向内核态转化效率低</li>
<li>只适合连接数少的场景</li>
</ul>
<h3 id="线程池版设计"><a href="#线程池版设计" class="headerlink" title="线程池版设计"></a>线程池版设计</h3><pre class="mermaid">graph TD
subgraph 线程池版
t4(thread) --> s4(socket1)
t5(thread) --> s5(socket2)
t4(thread) -.-> s6(socket3)
t5(thread) -.-> s7(socket4)
end</pre>
<h4 id="⚠️-线程池版缺点"><a href="#⚠️-线程池版缺点" class="headerlink" title="⚠️ 线程池版缺点"></a>⚠️ 线程池版缺点</h4><ul>
<li>阻塞模式下，线程仅能处理一个 socket 连接</li>
<li>仅适合短连接场景</li>
</ul>
<h3 id="selector-版设计"><a href="#selector-版设计" class="headerlink" title="selector 版设计"></a>selector 版设计</h3><p>selector 的作用就是配合一个线程来管理多个 channel，获取这些 channel 上发生的事件，这些 channel 工作在非阻塞模式下，不会让线程吊死在一个 channel 上。适合连接数特别多，但流量低的场景（low traffic）</p>
<pre class="mermaid">graph TD
subgraph selector 版
thread --> selector
selector --> c1(channel)
selector --> c2(channel)
selector --> c3(channel)
end</pre>



<p>调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件，这些事件发生，select 方法就会返回这些事件交给 thread 来处理</p>
<h1 id="2-ByteBuffer"><a href="#2-ByteBuffer" class="headerlink" title="2. ByteBuffer"></a>2. ByteBuffer</h1><p>有一普通文本文件 data.txt，内容为</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">1234567890abcd</span><br></pre></td></tr></table></figure>

<p>使用 FileChannel 来读取文件内容</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> java.io.RandomAccessFile;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@className</span>: com.nio.ChannelDemo1</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span>: Nio读取文件</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: tong.li</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@createTime</span>: 2021/5/24 16:01</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@copyright</span>: Tongcheng-Elong Holdings Limited</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChannelDemo1</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 使用try-resource管理RandomAccessFile</span></span><br><span class="line">        <span class="keyword">try</span> (<span class="type">RandomAccessFile</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(ChannelDemo1.class.getClassLoader()</span><br><span class="line">                .getResource(<span class="string">&quot;files/ChannelDemo1.txt&quot;</span>).getPath(), <span class="string">&quot;r&quot;</span>)) &#123;</span><br><span class="line">            <span class="comment">// 获取Channel</span></span><br><span class="line">            <span class="type">FileChannel</span> <span class="variable">channel</span> <span class="operator">=</span> file.getChannel();</span><br><span class="line">            <span class="comment">// 创建10个字节的buffer</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">10</span>);</span><br><span class="line">            <span class="comment">// 循环读取</span></span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="comment">// 向buffer中写入数据，默认是写模式</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> channel.read(buffer);</span><br><span class="line">                <span class="comment">// 如果读取到-1说明已读完</span></span><br><span class="line">                log.debug(<span class="string">&quot;读到字节数：&#123;&#125;&quot;</span>, len);</span><br><span class="line">                <span class="keyword">if</span> (len == -<span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 切换为读模式,默认buffer是写入模式</span></span><br><span class="line">                buffer.flip();</span><br><span class="line">                <span class="keyword">while</span> (buffer.hasRemaining()) &#123;</span><br><span class="line">                    log.info(<span class="string">&quot;content: &quot;</span>  + (<span class="type">char</span>)buffer.get());</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 切换写模式</span></span><br><span class="line">                buffer.clear();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">           log.error(<span class="string">&quot;读取错误&quot;</span>,e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 读到字节数：10</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 1</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 2</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 3</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 4</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 5</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 6</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 7</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 8</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 9</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 0</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 读到字节数：4</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - a</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - b</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - c</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - d</span><br><span class="line">10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 读到字节数：-1</span><br></pre></td></tr></table></figure>



<h2 id="2-1-ByteBuffer-正确使用姿势"><a href="#2-1-ByteBuffer-正确使用姿势" class="headerlink" title="2.1  ByteBuffer 正确使用姿势"></a>2.1  ByteBuffer 正确使用姿势</h2><ol>
<li>向 buffer 写入数据，例如调用 channel.read(buffer)</li>
<li>调用 flip() 切换至<strong>读模式</strong></li>
<li>从 buffer 读取数据，例如调用 buffer.get()</li>
<li>调用 clear() 或 compact() 切换至<strong>写模式</strong></li>
<li>重复 1~4 步骤</li>
</ol>
<h2 id="2-2-ByteBuffer-结构"><a href="#2-2-ByteBuffer-结构" class="headerlink" title="2.2 ByteBuffer 结构"></a>2.2 ByteBuffer 结构</h2><p>ByteBuffer 有以下重要属性</p>
<ul>
<li>capacity</li>
<li>position</li>
<li>limit</li>
</ul>
<p>一开始</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804145257.png"></p>
<p>写模式下，position 是写入位置，limit 等于容量，下图表示写入了 4 个字节后的状态</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804145339.png"></p>
<p>flip 动作发生后，position 切换为读取位置，limit 切换为读取限制</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804145437.png"></p>
<p>读取 4 个字节后，状态</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804145524.png"></p>
<p>clear 动作发生后，状态</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804145554.png"></p>
<p>compact 方法，是把未读完的部分向前压缩，然后切换至写模式</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804145607.png"></p>
<h3 id="💡-调试工具类"><a href="#💡-调试工具类" class="headerlink" title="💡 调试工具类"></a>💡 调试工具类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ByteBufferUtil</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">char</span>[] BYTE2CHAR = <span class="keyword">new</span> <span class="title class_">char</span>[<span class="number">256</span>];</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">char</span>[] HEXDUMP_TABLE = <span class="keyword">new</span> <span class="title class_">char</span>[<span class="number">256</span> * <span class="number">4</span>];</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String[] HEXPADDING = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">16</span>];</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String[] HEXDUMP_ROWPREFIXES = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">65536</span> &gt;&gt;&gt; <span class="number">4</span>];</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String[] BYTE2HEX = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">256</span>];</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String[] BYTEPADDING = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">16</span>];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">char</span>[] DIGITS = <span class="string">&quot;0123456789abcdef&quot;</span>.toCharArray();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">256</span>; i++) &#123;</span><br><span class="line">            HEXDUMP_TABLE[i &lt;&lt; <span class="number">1</span>] = DIGITS[i &gt;&gt;&gt; <span class="number">4</span> &amp; <span class="number">0x0F</span>];</span><br><span class="line">            HEXDUMP_TABLE[(i &lt;&lt; <span class="number">1</span>) + <span class="number">1</span>] = DIGITS[i &amp; <span class="number">0x0F</span>];</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> i;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Generate the lookup table for hex dump paddings</span></span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; HEXPADDING.length; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">padding</span> <span class="operator">=</span> HEXPADDING.length - i;</span><br><span class="line">            <span class="type">StringBuilder</span> <span class="variable">buf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(padding * <span class="number">3</span>);</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; padding; j++) &#123;</span><br><span class="line">                buf.append(<span class="string">&quot;   &quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            HEXPADDING[i] = buf.toString();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Generate the lookup table for the start-offset header in each row (up to 64KiB).</span></span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; HEXDUMP_ROWPREFIXES.length; i++) &#123;</span><br><span class="line">            <span class="type">StringBuilder</span> <span class="variable">buf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">12</span>);</span><br><span class="line">            buf.append(NEWLINE);</span><br><span class="line">            buf.append(Long.toHexString(i &lt;&lt; <span class="number">4</span> &amp; <span class="number">0xFFFFFFFFL</span> | <span class="number">0x100000000L</span>));</span><br><span class="line">            buf.setCharAt(buf.length() - <span class="number">9</span>, <span class="string">&#x27;|&#x27;</span>);</span><br><span class="line">            buf.append(<span class="string">&#x27;|&#x27;</span>);</span><br><span class="line">            HEXDUMP_ROWPREFIXES[i] = buf.toString();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Generate the lookup table for byte-to-hex-dump conversion</span></span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; BYTE2HEX.length; i++) &#123;</span><br><span class="line">            BYTE2HEX[i] = <span class="string">&#x27; &#x27;</span> + StringUtil.byteToHexStringPadded(i);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Generate the lookup table for byte dump paddings</span></span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; BYTEPADDING.length; i++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">padding</span> <span class="operator">=</span> BYTEPADDING.length - i;</span><br><span class="line">            <span class="type">StringBuilder</span> <span class="variable">buf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(padding);</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; padding; j++) &#123;</span><br><span class="line">                buf.append(<span class="string">&#x27; &#x27;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            BYTEPADDING[i] = buf.toString();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Generate the lookup table for byte-to-char conversion</span></span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; BYTE2CHAR.length; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i &lt;= <span class="number">0x1f</span> || i &gt;= <span class="number">0x7f</span>) &#123;</span><br><span class="line">                BYTE2CHAR[i] = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                BYTE2CHAR[i] = (<span class="type">char</span>) i;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 打印所有内容</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> buffer</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">debugAll</span><span class="params">(ByteBuffer buffer)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">oldlimit</span> <span class="operator">=</span> buffer.limit();</span><br><span class="line">        buffer.limit(buffer.capacity());</span><br><span class="line">        <span class="type">StringBuilder</span> <span class="variable">origin</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">256</span>);</span><br><span class="line">        appendPrettyHexDump(origin, buffer, <span class="number">0</span>, buffer.capacity());</span><br><span class="line">        System.out.println(<span class="string">&quot;+--------+-------------------- all ------------------------+----------------+&quot;</span>);</span><br><span class="line">        System.out.printf(<span class="string">&quot;position: [%d], limit: [%d]\n&quot;</span>, buffer.position(), oldlimit);</span><br><span class="line">        System.out.println(origin);</span><br><span class="line">        buffer.limit(oldlimit);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 打印可读取内容</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> buffer</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">debugRead</span><span class="params">(ByteBuffer buffer)</span> &#123;</span><br><span class="line">        <span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">256</span>);</span><br><span class="line">        appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position());</span><br><span class="line">        System.out.println(<span class="string">&quot;+--------+-------------------- read -----------------------+----------------+&quot;</span>);</span><br><span class="line">        System.out.printf(<span class="string">&quot;position: [%d], limit: [%d]\n&quot;</span>, buffer.position(), buffer.limit());</span><br><span class="line">        System.out.println(builder);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">appendPrettyHexDump</span><span class="params">(StringBuilder dump, ByteBuffer buf, <span class="type">int</span> offset, <span class="type">int</span> length)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (isOutOfBounds(offset, length, buf.capacity())) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IndexOutOfBoundsException</span>(</span><br><span class="line">                    <span class="string">&quot;expected: &quot;</span> + <span class="string">&quot;0 &lt;= offset(&quot;</span> + offset + <span class="string">&quot;) &lt;= offset + length(&quot;</span> + length</span><br><span class="line">                            + <span class="string">&quot;) &lt;= &quot;</span> + <span class="string">&quot;buf.capacity(&quot;</span> + buf.capacity() + <span class="string">&#x27;)&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (length == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        dump.append(</span><br><span class="line">                <span class="string">&quot;         +-------------------------------------------------+&quot;</span> +</span><br><span class="line">                        NEWLINE + <span class="string">&quot;         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |&quot;</span> +</span><br><span class="line">                        NEWLINE + <span class="string">&quot;+--------+-------------------------------------------------+----------------+&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">startIndex</span> <span class="operator">=</span> offset;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">fullRows</span> <span class="operator">=</span> length &gt;&gt;&gt; <span class="number">4</span>;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">remainder</span> <span class="operator">=</span> length &amp; <span class="number">0xF</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Dump the rows which have 16 bytes.</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">row</span> <span class="operator">=</span> <span class="number">0</span>; row &lt; fullRows; row++) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">rowStartIndex</span> <span class="operator">=</span> (row &lt;&lt; <span class="number">4</span>) + startIndex;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Per-row prefix.</span></span><br><span class="line">            appendHexDumpRowPrefix(dump, row, rowStartIndex);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Hex dump</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">rowEndIndex</span> <span class="operator">=</span> rowStartIndex + <span class="number">16</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> rowStartIndex; j &lt; rowEndIndex; j++) &#123;</span><br><span class="line">                dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);</span><br><span class="line">            &#125;</span><br><span class="line">            dump.append(<span class="string">&quot; |&quot;</span>);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// ASCII dump</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> rowStartIndex; j &lt; rowEndIndex; j++) &#123;</span><br><span class="line">                dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);</span><br><span class="line">            &#125;</span><br><span class="line">            dump.append(<span class="string">&#x27;|&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Dump the last row which has less than 16 bytes.</span></span><br><span class="line">        <span class="keyword">if</span> (remainder != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">rowStartIndex</span> <span class="operator">=</span> (fullRows &lt;&lt; <span class="number">4</span>) + startIndex;</span><br><span class="line">            appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Hex dump</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">rowEndIndex</span> <span class="operator">=</span> rowStartIndex + remainder;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> rowStartIndex; j &lt; rowEndIndex; j++) &#123;</span><br><span class="line">                dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);</span><br><span class="line">            &#125;</span><br><span class="line">            dump.append(HEXPADDING[remainder]);</span><br><span class="line">            dump.append(<span class="string">&quot; |&quot;</span>);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Ascii dump</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> rowStartIndex; j &lt; rowEndIndex; j++) &#123;</span><br><span class="line">                dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);</span><br><span class="line">            &#125;</span><br><span class="line">            dump.append(BYTEPADDING[remainder]);</span><br><span class="line">            dump.append(<span class="string">&#x27;|&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        dump.append(NEWLINE +</span><br><span class="line">                <span class="string">&quot;+--------+-------------------------------------------------+----------------+&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">appendHexDumpRowPrefix</span><span class="params">(StringBuilder dump, <span class="type">int</span> row, <span class="type">int</span> rowStartIndex)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (row &lt; HEXDUMP_ROWPREFIXES.length) &#123;</span><br><span class="line">            dump.append(HEXDUMP_ROWPREFIXES[row]);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            dump.append(NEWLINE);</span><br><span class="line">            dump.append(Long.toHexString(rowStartIndex &amp; <span class="number">0xFFFFFFFFL</span> | <span class="number">0x100000000L</span>));</span><br><span class="line">            dump.setCharAt(dump.length() - <span class="number">9</span>, <span class="string">&#x27;|&#x27;</span>);</span><br><span class="line">            dump.append(<span class="string">&#x27;|&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">short</span> <span class="title function_">getUnsignedByte</span><span class="params">(ByteBuffer buffer, <span class="type">int</span> index)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (<span class="type">short</span>) (buffer.get(index) &amp; <span class="number">0xFF</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="2-3-ByteBuffer-常见方法"><a href="#2-3-ByteBuffer-常见方法" class="headerlink" title="2.3 ByteBuffer 常见方法"></a>2.3 ByteBuffer 常见方法</h2><h3 id="分配空间"><a href="#分配空间" class="headerlink" title="分配空间"></a>分配空间</h3><p>可以使用 allocate 方法为 ByteBuffer 分配空间，其它 buffer 类也有该方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Bytebuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br></pre></td></tr></table></figure>

<h3 id="向-buffer-写入数据"><a href="#向-buffer-写入数据" class="headerlink" title="向 buffer 写入数据"></a>向 buffer 写入数据</h3><p>有两种办法</p>
<ul>
<li>调用 channel 的 read 方法</li>
<li>调用 buffer 自己的 put 方法</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">readBytes</span> <span class="operator">=</span> channel.read(buf);</span><br></pre></td></tr></table></figure>

<p>和</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">buf.put((<span class="type">byte</span>)<span class="number">127</span>);</span><br></pre></td></tr></table></figure>



<h3 id="从-buffer-读取数据"><a href="#从-buffer-读取数据" class="headerlink" title="从 buffer 读取数据"></a>从 buffer 读取数据</h3><p>同样有两种办法</p>
<ul>
<li>调用 channel 的 write 方法</li>
<li>调用 buffer 自己的 get 方法</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">writeBytes</span> <span class="operator">=</span> channel.write(buf);</span><br></pre></td></tr></table></figure>

<p>和</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">byte</span> <span class="variable">b</span> <span class="operator">=</span> buf.get();</span><br></pre></td></tr></table></figure>

<p>get 方法会让 position 读指针向后走，如果想重复读取数据</p>
<ul>
<li>可以调用 rewind 方法将 position 重新置为 0</li>
<li>或者调用 get(int i) 方法获取索引 i 的内容，它不会移动读指针</li>
</ul>
<h3 id="mark-和-reset"><a href="#mark-和-reset" class="headerlink" title="mark 和 reset"></a>mark 和 reset</h3><p>mark 是在读取时，做一个标记，即使 position 改变，只要调用 reset 就能回到 mark 的位置</p>
<blockquote>
<p><strong>注意</strong></p>
<p>rewind 和 flip 都会清除 mark 位置</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.utils.ByteBufferUtil;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@className</span>: com.nio.ByteBufferDemo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span>: ByteBufferDemo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: tong.li</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@createTime</span>: 2021/5/24 17:18</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@copyright</span>: Tongcheng-Elong Holdings Limited</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ByteBufferDemo</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br><span class="line">        <span class="comment">// 往buffer添加数据</span></span><br><span class="line">        buffer.put((<span class="type">byte</span>)<span class="string">&#x27;a&#x27;</span>);</span><br><span class="line">        <span class="comment">// 切换为读模式</span></span><br><span class="line">        buffer.flip();</span><br><span class="line">        <span class="comment">// 读取指定位置的数据，position不移位</span></span><br><span class="line">        <span class="type">byte</span> <span class="variable">b</span> <span class="operator">=</span> buffer.get(<span class="number">0</span>);</span><br><span class="line">        log.info(<span class="string">&quot;读取到的数据：&quot;</span> + b + <span class="string">&quot;, 当前position为: &quot;</span> + buffer.position());</span><br><span class="line">        <span class="type">byte</span> <span class="variable">b1</span> <span class="operator">=</span> buffer.get();</span><br><span class="line">        log.info(<span class="string">&quot;读取到的数据：&quot;</span> + b1 + <span class="string">&quot;, 当前position为: &quot;</span> + buffer.position());</span><br><span class="line">        <span class="comment">// 重置position为0</span></span><br><span class="line">        buffer.rewind();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">b2</span> <span class="operator">=</span> buffer.get(<span class="number">0</span>);</span><br><span class="line">        log.info(<span class="string">&quot;重置position后读到的数据：&quot;</span> + b2 + <span class="string">&quot;, 当前position为: &quot;</span> + buffer.position());</span><br><span class="line">        <span class="comment">// 切换为写模式，多写几个字符进行mark与reset操作</span></span><br><span class="line">        <span class="comment">// clear()是将position=0, limit = capacity,mark=-1</span></span><br><span class="line">        <span class="comment">// buffer.clear();</span></span><br><span class="line">        <span class="comment">// compact是将未读完数据移位到首部</span></span><br><span class="line">        buffer.compact();</span><br><span class="line">        buffer.put(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="string">&#x27;b&#x27;</span>,<span class="string">&#x27;c&#x27;</span>,<span class="string">&#x27;d&#x27;</span>&#125;);</span><br><span class="line">        <span class="comment">// 切换为写模式</span></span><br><span class="line">        buffer.flip();</span><br><span class="line">        <span class="comment">// 读取a字符</span></span><br><span class="line">        <span class="type">byte</span> <span class="variable">b3</span> <span class="operator">=</span> buffer.get();</span><br><span class="line">        <span class="comment">// 读取b字符</span></span><br><span class="line">        <span class="type">byte</span> <span class="variable">b4</span> <span class="operator">=</span> buffer.get();</span><br><span class="line">        <span class="comment">// 读取c字符</span></span><br><span class="line">        <span class="comment">// 做个标记，重新读取字符c</span></span><br><span class="line">        buffer.mark();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">b5</span> <span class="operator">=</span> buffer.get();</span><br><span class="line">        <span class="comment">// 重置到mark位置上</span></span><br><span class="line">        buffer.reset();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">b6</span> <span class="operator">=</span> buffer.get();</span><br><span class="line">        log.info(<span class="string">&quot;mark-reset后的字符，before：&quot;</span> + b5 + <span class="string">&quot;, after: &quot;</span> + b6);</span><br><span class="line">        ByteBufferUtil.debugAll(buffer);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="字符串与-ByteBuffer-互转"><a href="#字符串与-ByteBuffer-互转" class="headerlink" title="字符串与 ByteBuffer 互转"></a>字符串与 ByteBuffer 互转</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="comment">// 字符串转ByteBuffer</span></span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">buffer1</span> <span class="operator">=</span> StandardCharsets.UTF_8.encode(<span class="string">&quot;abc&quot;</span>);</span><br><span class="line">    ByteBufferUtil.debugAll(buffer1);</span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">buffer2</span> <span class="operator">=</span> Charset.defaultCharset().encode(<span class="string">&quot;你好&quot;</span>);</span><br><span class="line">    ByteBufferUtil.debugAll(buffer2);</span><br><span class="line">    <span class="comment">// ByteBuffer转字符串</span></span><br><span class="line">    log.info(<span class="string">&quot;buffer1 to string:&quot;</span> + StandardCharsets.UTF_8.decode(buffer1));</span><br><span class="line">    log.info(<span class="string">&quot;buffer2 to string:&quot;</span> + StandardCharsets.UTF_8.decode(buffer2));</span><br><span class="line">    <span class="comment">// 都属于堆内存：java.nio.HeapByteBuffer</span></span><br><span class="line">    log.info(buffer1.getClass().toString());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| e4 bd a0 e5 a5 bd                               |......          |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| e4 bd a0 e5 a5 bd                               |......          |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">class java.nio.HeapCharBuffer</span><br><span class="line">你好</span><br></pre></td></tr></table></figure>



<h5 id="⚠️-Buffer-的线程安全"><a href="#⚠️-Buffer-的线程安全" class="headerlink" title="⚠️ Buffer 的线程安全"></a>⚠️ Buffer 的线程安全</h5><blockquote>
<p>Buffer 是<strong>非线程安全的</strong></p>
</blockquote>
<h2 id="2-4-Scattering-Reads"><a href="#2-4-Scattering-Reads" class="headerlink" title="2.4 Scattering Reads"></a>2.4 Scattering Reads</h2><p>分散读取，有一个文本文件 3parts.txt</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">onetwothree</span><br></pre></td></tr></table></figure>

<p>使用如下方式读取，可以将数据填充至多个 buffer</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.nio;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.utils.ByteBufferUtil;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.RandomAccessFile;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@className</span>: com.nio.ScatteringReadsDemo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span>: 多个ByteBuffer分散读</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: tong.li</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@createTime</span>: 2021/5/24 16:01</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@copyright</span>: Tongcheng-Elong Holdings Limited</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ScatteringReadsDemo</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 使用try-resource管理RandomAccessFile</span></span><br><span class="line">        <span class="keyword">try</span> (<span class="type">RandomAccessFile</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(ScatteringReadsDemo.class.getClassLoader()</span><br><span class="line">                .getResource(<span class="string">&quot;files/3parts.txt&quot;</span>).getPath(), <span class="string">&quot;r&quot;</span>)) &#123;</span><br><span class="line">            <span class="comment">// 获取Channel</span></span><br><span class="line">            <span class="type">FileChannel</span> <span class="variable">channel</span> <span class="operator">=</span> file.getChannel();</span><br><span class="line">            <span class="comment">// 创建buffer1，存放one三个字节</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer1</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">3</span>);</span><br><span class="line">            <span class="comment">// 创建buffer2，存放two三个字节</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer2</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">3</span>);</span><br><span class="line">            <span class="comment">// 创建buffer3，存放three五个字节</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer3</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">5</span>);</span><br><span class="line">            <span class="comment">// 从Channel中分散读取字节数往三个buffer写数据</span></span><br><span class="line">            channel.read(<span class="keyword">new</span> <span class="title class_">ByteBuffer</span>[]&#123;buffer1,buffer2,buffer3&#125;);</span><br><span class="line">            <span class="comment">// 将三个buffer都切换为读模式</span></span><br><span class="line">            buffer1.flip();</span><br><span class="line">            buffer2.flip();</span><br><span class="line">            buffer3.flip();</span><br><span class="line">            <span class="comment">// 打印读取结果</span></span><br><span class="line">            ByteBufferUtil.debugAll(buffer1);</span><br><span class="line">            ByteBufferUtil.debugAll(buffer2);</span><br><span class="line">            ByteBufferUtil.debugAll(buffer3);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">           log.error(<span class="string">&quot;读取错误&quot;</span>,e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>结果</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">+--------+-------------------- all ------------------------+----------------+</span><br><span class="line">position: [0], <span class="built_in">limit</span>: [3]</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 6f 6e 65                                        |one             |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">+--------+-------------------- all ------------------------+----------------+</span><br><span class="line">position: [0], <span class="built_in">limit</span>: [3]</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 74 77 6f                                        |two             |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">+--------+-------------------- all ------------------------+----------------+</span><br><span class="line">position: [0], <span class="built_in">limit</span>: [5]</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 74 68 72 65 65                                  |three           |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br></pre></td></tr></table></figure>



<h2 id="2-5-Gathering-Writes"><a href="#2-5-Gathering-Writes" class="headerlink" title="2.5 Gathering Writes"></a>2.5 Gathering Writes</h2><p>使用如下方式写入，可以将多个 buffer 的数据填充至 channel</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.nio;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.utils.ByteBufferUtil;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> java.io.RandomAccessFile;</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@className</span>: com.nio.GatheringReadsDemo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span>: 多个ByteBuffer分散写</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: tong.li</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@createTime</span>: 2021/5/24 16:01</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@copyright</span>: Tongcheng-Elong Holdings Limited</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GatheringReadsDemo</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 使用try-resource管理RandomAccessFile</span></span><br><span class="line">        <span class="keyword">try</span> (<span class="type">RandomAccessFile</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(GatheringReadsDemo.class.getClassLoader()</span><br><span class="line">                .getResource(<span class="string">&quot;files/3parts.txt&quot;</span>).getPath(), <span class="string">&quot;rw&quot;</span>)) &#123;</span><br><span class="line">            <span class="comment">// 获取Channel</span></span><br><span class="line">            <span class="type">FileChannel</span> <span class="variable">channel</span> <span class="operator">=</span> file.getChannel();</span><br><span class="line">            <span class="comment">// 创建buffer1，存放four四个字节</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer1</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">4</span>);</span><br><span class="line">            <span class="comment">// 创建buffer2，存放five四个字节</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer2</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">4</span>);</span><br><span class="line">            <span class="comment">// 从第11个位置开始写，因为onetowthree已占用11个字节数</span></span><br><span class="line">            channel.position(<span class="number">11</span>);</span><br><span class="line">            buffer1.put(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="string">&#x27;f&#x27;</span>,<span class="string">&#x27;o&#x27;</span>,<span class="string">&#x27;u&#x27;</span>,<span class="string">&#x27;r&#x27;</span>&#125;);</span><br><span class="line">            buffer2.put(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="string">&#x27;f&#x27;</span>,<span class="string">&#x27;i&#x27;</span>,<span class="string">&#x27;v&#x27;</span>,<span class="string">&#x27;e&#x27;</span>&#125;);</span><br><span class="line">            <span class="comment">// 将三个buffer都切换为读模式</span></span><br><span class="line">            buffer1.flip();</span><br><span class="line">            buffer2.flip();</span><br><span class="line">            <span class="comment">// 打印读取结果</span></span><br><span class="line">            ByteBufferUtil.debugAll(buffer1);</span><br><span class="line">            ByteBufferUtil.debugAll(buffer2);</span><br><span class="line">            <span class="comment">// 从Channel中分散读取字节数往三个buffer写数据</span></span><br><span class="line">            channel.write(<span class="keyword">new</span> <span class="title class_">ByteBuffer</span>[]&#123;buffer1,buffer2&#125;);</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">           log.error(<span class="string">&quot;读取错误&quot;</span>,e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">+--------+-------------------- all ------------------------+----------------+</span><br><span class="line">position: [0], <span class="built_in">limit</span>: [4]</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 66 6f 75 72                                     |four            |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">+--------+-------------------- all ------------------------+----------------+</span><br><span class="line">position: [0], <span class="built_in">limit</span>: [4]</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 66 69 76 65                                     |five            |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br></pre></td></tr></table></figure>

<p>文件内容</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">onetwothreefourfive</span><br></pre></td></tr></table></figure>



<h2 id="2-6-练习"><a href="#2-6-练习" class="headerlink" title="2.6 练习"></a>2.6 练习</h2><p>网络上有多条数据发送给服务端，数据之间使用换行符 \n 进行分隔<br>但由于某种原因这些数据在接收时，被进行了重新组合，例如原始数据有3条为</p>
<ul>
<li>Hello,world\n</li>
<li>I’m zhangsan\n</li>
<li>How are you?\n</li>
</ul>
<p>变成了下面的两个 byteBuffer (黏包，半包)</p>
<ul>
<li>Hello,world\nI’m zhangsan\nHo</li>
<li>w are you?\n</li>
</ul>
<p>现在要求你编写程序，将错乱的数据恢复成原始的按 \n 分隔的数据</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">source</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">32</span>);</span><br><span class="line">    <span class="comment">//                     11            24</span></span><br><span class="line">    source.put(<span class="string">&quot;Hello,world\nI&#x27;m zhangsan\nHo&quot;</span>.getBytes());</span><br><span class="line">    split(source);</span><br><span class="line"></span><br><span class="line">    source.put(<span class="string">&quot;w are you?\nhaha!\n&quot;</span>.getBytes());</span><br><span class="line">    split(source);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 通过换行符解决半包、粘包问题</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> source</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">split</span><span class="params">(ByteBuffer source)</span> &#123;</span><br><span class="line">    source.flip();</span><br><span class="line">    <span class="type">int</span> <span class="variable">oldLimit</span> <span class="operator">=</span> source.limit();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; oldLimit; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (source.get(i) == <span class="string">&#x27;\n&#x27;</span>) &#123;</span><br><span class="line">            System.out.println(i);</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">target</span> <span class="operator">=</span> ByteBuffer.allocate(i + <span class="number">1</span> - source.position());</span><br><span class="line">            <span class="comment">// 0 ~ limit</span></span><br><span class="line">            source.limit(i + <span class="number">1</span>);</span><br><span class="line">            target.put(source); <span class="comment">// 从source 读，向 target 写</span></span><br><span class="line">            debugAll(target);</span><br><span class="line">            source.limit(oldLimit);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    source.compact();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">splitStronger</span><span class="params">(ByteBuffer source)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (source == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    source.flip();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; source.limit(); i++) &#123;</span><br><span class="line">        <span class="comment">// 找到一条完整消息</span></span><br><span class="line">        <span class="keyword">if</span> (source.get(i) == <span class="string">&#x27;\n&#x27;</span>) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> i + <span class="number">1</span> - source.position();</span><br><span class="line">            <span class="comment">// 把这条完整消息存入新的 ByteBuffer</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">target</span> <span class="operator">=</span> ByteBuffer.allocate(length);</span><br><span class="line">            <span class="comment">// 从 source 读，向 target 写</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; length; j++) &#123;</span><br><span class="line">                target.put(source.get());</span><br><span class="line">            &#125;</span><br><span class="line">            ByteBufferUtil.debugAll(target);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    source.compact(); <span class="comment">// 0123456789abcdef  position 16 limit 16</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="3-文件编程"><a href="#3-文件编程" class="headerlink" title="3. 文件编程"></a>3. 文件编程</h1><h2 id="3-1-FileChannel"><a href="#3-1-FileChannel" class="headerlink" title="3.1 FileChannel"></a>3.1 FileChannel</h2><h3 id="⚠️-FileChannel-工作模式"><a href="#⚠️-FileChannel-工作模式" class="headerlink" title="⚠️ FileChannel 工作模式"></a>⚠️ FileChannel 工作模式</h3><blockquote>
<p>FileChannel 只能工作在阻塞模式下</p>
</blockquote>
<h3 id="获取"><a href="#获取" class="headerlink" title="获取"></a>获取</h3><p>不能直接打开 FileChannel，必须通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel，它们都有 getChannel 方法</p>
<ul>
<li>通过 FileInputStream 获取的 channel 只能读</li>
<li>通过 FileOutputStream 获取的 channel 只能写</li>
<li>通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定</li>
</ul>
<h3 id="读取"><a href="#读取" class="headerlink" title="读取"></a>读取</h3><p>会从 channel 读取数据填充 ByteBuffer，返回值表示读到了多少字节，-1 表示到达了文件的末尾</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">readBytes</span> <span class="operator">=</span> channel.read(buffer);</span><br></pre></td></tr></table></figure>



<h3 id="写入"><a href="#写入" class="headerlink" title="写入"></a>写入</h3><p>写入的正确姿势如下， SocketChannel</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ...;</span><br><span class="line">buffer.put(...); <span class="comment">// 存入数据</span></span><br><span class="line">buffer.flip();   <span class="comment">// 切换读模式</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span>(buffer.hasRemaining()) &#123;</span><br><span class="line">    channel.write(buffer);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在 while 中调用 channel.write 是因为 write 方法并不能保证一次将 buffer 中的内容全部写入 channel</p>
<h3 id="关闭"><a href="#关闭" class="headerlink" title="关闭"></a>关闭</h3><p>channel 必须关闭，不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法会间接地调用 channel 的 close 方法</p>
<h3 id="位置"><a href="#位置" class="headerlink" title="位置"></a>位置</h3><p>获取当前位置</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">pos</span> <span class="operator">=</span> channel.position();</span><br></pre></td></tr></table></figure>

<p>设置当前位置</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">newPos</span> <span class="operator">=</span> ...;</span><br><span class="line">channel.position(newPos);</span><br></pre></td></tr></table></figure>

<p>设置当前位置时，如果设置为文件的末尾</p>
<ul>
<li>这时读取会返回 -1 </li>
<li>这时写入，会追加内容，但要注意如果 position 超过了文件末尾，再写入时在新内容和原末尾之间会有空洞（00）</li>
</ul>
<h3 id="大小"><a href="#大小" class="headerlink" title="大小"></a>大小</h3><p>使用 size 方法获取文件的大小</p>
<h3 id="强制写入"><a href="#强制写入" class="headerlink" title="强制写入"></a>强制写入</h3><p>操作系统出于性能的考虑，会将数据缓存，不是立刻写入磁盘。可以调用 force(true)  方法将文件内容和元数据（文件的权限等信息）立刻写入磁盘</p>
<h2 id="3-2-两个-Channel-传输数据"><a href="#3-2-两个-Channel-传输数据" class="headerlink" title="3.2 两个 Channel 传输数据"></a>3.2 两个 Channel 传输数据</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.nio;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.nio.channels.FileChannel;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@className</span>: com.nio.FileChannelDemo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span>: 文件Channel演示</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: tong.li</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@createTime</span>: 2021/5/25 16:17</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@copyright</span>: Tongcheng-Elong Holdings Limited</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FileChannelDemo</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">FileChannel</span> <span class="variable">inputChannel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(FileChannelDemo.class.getClassLoader().getResource(<span class="string">&quot;files/3parts.txt&quot;</span>).getPath()).getChannel();</span><br><span class="line">             <span class="type">FileChannel</span> <span class="variable">outputChannel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;D:\\learning\\code\\netty-study\\src\\main\\resources\\files\\to.txt&quot;</span>).getChannel();</span><br><span class="line">        ) &#123;</span><br><span class="line">            inputChannel.transferTo(<span class="number">0</span>,inputChannel.size(),outputChannel);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        log.info(<span class="string">&quot;transferTo 用时：&#123;&#125;&quot;</span>,(end - start));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">transferTo 用时：0.2011</span><br></pre></td></tr></table></figure>



<p>超过 2g 大小的文件传输</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestFileChannelTransferTo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (</span><br><span class="line">                <span class="type">FileChannel</span> <span class="variable">from</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;data.txt&quot;</span>).getChannel();</span><br><span class="line">                <span class="type">FileChannel</span> <span class="variable">to</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;to.txt&quot;</span>).getChannel();</span><br><span class="line">        ) &#123;</span><br><span class="line">            <span class="comment">// 效率高，底层会利用操作系统的零拷贝进行优化</span></span><br><span class="line">            <span class="type">long</span> <span class="variable">size</span> <span class="operator">=</span> from.size();</span><br><span class="line">            <span class="comment">// left 变量代表还剩余多少字节</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">long</span> <span class="variable">left</span> <span class="operator">=</span> size; left &gt; <span class="number">0</span>; ) &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;position:&quot;</span> + (size - left) + <span class="string">&quot; left:&quot;</span> + left);</span><br><span class="line">                left -= from.transferTo((size - left), left, to);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>实际传输一个超大文件</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">position:0 left:7769948160</span><br><span class="line">position:2147483647 left:5622464513</span><br><span class="line">position:4294967294 left:3474980866</span><br><span class="line">position:6442450941 left:1327497219</span><br></pre></td></tr></table></figure>



<h2 id="3-3-Path"><a href="#3-3-Path" class="headerlink" title="3.3 Path"></a>3.3 Path</h2><p>jdk7 引入了 Path 和 Paths 类</p>
<ul>
<li>Path 用来表示文件路径</li>
<li>Paths 是工具类，用来获取 Path 实例</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;1.txt&quot;</span>); <span class="comment">// 相对路径 使用 user.dir 环境变量来定位 1.txt</span></span><br><span class="line"></span><br><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:\\1.txt&quot;</span>); <span class="comment">// 绝对路径 代表了  d:\1.txt</span></span><br><span class="line"></span><br><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:/1.txt&quot;</span>); <span class="comment">// 绝对路径 同样代表了  d:\1.txt</span></span><br><span class="line"></span><br><span class="line"><span class="type">Path</span> <span class="variable">projects</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:\\data&quot;</span>, <span class="string">&quot;projects&quot;</span>); <span class="comment">// 代表了  d:\data\projects</span></span><br></pre></td></tr></table></figure>

<ul>
<li><code>.</code> 代表了当前路径</li>
<li><code>..</code> 代表了上一级路径</li>
</ul>
<p>例如目录结构如下</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">d:</span><br><span class="line">	|- data</span><br><span class="line">		|- projects</span><br><span class="line">			|- a</span><br><span class="line">			|- b</span><br></pre></td></tr></table></figure>

<p>代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:\\data\\projects\\a\\..\\b&quot;</span>);</span><br><span class="line">System.out.println(path);</span><br><span class="line">System.out.println(path.normalize()); <span class="comment">// 正常化路径</span></span><br></pre></td></tr></table></figure>

<p>会输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">d:\data\projects\a\..\b</span><br><span class="line">d:\data\projects\b</span><br></pre></td></tr></table></figure>



<h2 id="3-4-Files"><a href="#3-4-Files" class="headerlink" title="3.4 Files"></a>3.4 Files</h2><p>检查文件是否存在</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line">System.out.println(Files.exists(path));</span><br></pre></td></tr></table></figure>



<p>创建一级目录</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/d1&quot;</span>);</span><br><span class="line">Files.createDirectory(path);</span><br></pre></td></tr></table></figure>

<ul>
<li>如果目录已存在，会抛异常 FileAlreadyExistsException</li>
<li>不能一次创建多级目录，否则会抛异常 NoSuchFileException</li>
</ul>
<p>创建多级目录用</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/d1/d2&quot;</span>);</span><br><span class="line">Files.createDirectories(path);</span><br></pre></td></tr></table></figure>



<p>拷贝文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line"><span class="type">Path</span> <span class="variable">target</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/target.txt&quot;</span>);</span><br><span class="line"></span><br><span class="line">Files.copy(source, target);</span><br></pre></td></tr></table></figure>

<ul>
<li>如果文件已存在，会抛异常 FileAlreadyExistsException</li>
</ul>
<p>如果希望用 source 覆盖掉 target，需要用 StandardCopyOption 来控制</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);</span><br></pre></td></tr></table></figure>



<p>移动文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line"><span class="type">Path</span> <span class="variable">target</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line"></span><br><span class="line">Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);</span><br></pre></td></tr></table></figure>

<ul>
<li>StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性</li>
</ul>
<p>删除文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">target</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/target.txt&quot;</span>);</span><br><span class="line"></span><br><span class="line">Files.delete(target);</span><br></pre></td></tr></table></figure>

<ul>
<li>如果文件不存在，会抛异常 NoSuchFileException</li>
</ul>
<p>删除目录</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">target</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/d1&quot;</span>);</span><br><span class="line"></span><br><span class="line">Files.delete(target);</span><br></pre></td></tr></table></figure>

<ul>
<li>如果目录还有内容，会抛异常 DirectoryNotEmptyException</li>
</ul>
<p>遍历目录文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;C:\\Program Files\\Java\\jdk1.8.0_91&quot;</span>);</span><br><span class="line">    <span class="type">AtomicInteger</span> <span class="variable">dirCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line">    <span class="type">AtomicInteger</span> <span class="variable">fileCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line">    Files.walkFileTree(path, <span class="keyword">new</span> <span class="title class_">SimpleFileVisitor</span>&lt;Path&gt;()&#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> FileVisitResult <span class="title function_">preVisitDirectory</span><span class="params">(Path dir, BasicFileAttributes attrs)</span> </span><br><span class="line">            <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">            System.out.println(dir);</span><br><span class="line">            dirCount.incrementAndGet();</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">super</span>.preVisitDirectory(dir, attrs);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> FileVisitResult <span class="title function_">visitFile</span><span class="params">(Path file, BasicFileAttributes attrs)</span> </span><br><span class="line">            <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">            System.out.println(file);</span><br><span class="line">            fileCount.incrementAndGet();</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">super</span>.visitFile(file, attrs);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    System.out.println(dirCount); <span class="comment">// 133</span></span><br><span class="line">    System.out.println(fileCount); <span class="comment">// 1479</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>统计 jar 的数目</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;D:\\program\\sdk\\jdk\\jdk1.8.0_291&quot;</span>);</span><br><span class="line"><span class="type">AtomicInteger</span> <span class="variable">fileCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line">Files.walkFileTree(path, <span class="keyword">new</span> <span class="title class_">SimpleFileVisitor</span>&lt;Path&gt;()&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> FileVisitResult <span class="title function_">visitFile</span><span class="params">(Path file, BasicFileAttributes attrs)</span> </span><br><span class="line">        <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">if</span> (file.toFile().getName().endsWith(<span class="string">&quot;.jar&quot;</span>)) &#123;</span><br><span class="line">            fileCount.incrementAndGet();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.visitFile(file, attrs);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line">System.out.println(fileCount); <span class="comment">// 724</span></span><br></pre></td></tr></table></figure>



<p>删除多级目录</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:\\a&quot;</span>);</span><br><span class="line">Files.walkFileTree(path, <span class="keyword">new</span> <span class="title class_">SimpleFileVisitor</span>&lt;Path&gt;()&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> FileVisitResult <span class="title function_">visitFile</span><span class="params">(Path file, BasicFileAttributes attrs)</span> </span><br><span class="line">        <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        Files.delete(file);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.visitFile(file, attrs);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> FileVisitResult <span class="title function_">postVisitDirectory</span><span class="params">(Path dir, IOException exc)</span> </span><br><span class="line">        <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        Files.delete(dir);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.postVisitDirectory(dir, exc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<h3 id="⚠️-删除很危险"><a href="#⚠️-删除很危险" class="headerlink" title="⚠️ 删除很危险"></a>⚠️ 删除很危险</h3><blockquote>
<p>删除是危险操作，确保要递归删除的文件夹没有重要内容</p>
</blockquote>
<p>拷贝多级目录</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"><span class="type">String</span> <span class="variable">source</span> <span class="operator">=</span> <span class="string">&quot;D:\\Snipaste-1.16.2-x64&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">target</span> <span class="operator">=</span> <span class="string">&quot;D:\\Snipaste-1.16.2-x64aaa&quot;</span>;</span><br><span class="line"></span><br><span class="line">Files.walk(Paths.get(source)).forEach(path -&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">targetName</span> <span class="operator">=</span> path.toString().replace(source, target);</span><br><span class="line">        <span class="comment">// 是目录</span></span><br><span class="line">        <span class="keyword">if</span> (Files.isDirectory(path)) &#123;</span><br><span class="line">            Files.createDirectory(Paths.get(targetName));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 是普通文件</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Files.isRegularFile(path)) &#123;</span><br><span class="line">            Files.copy(path, Paths.get(targetName));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">System.out.println(end - start);</span><br></pre></td></tr></table></figure>

<h1 id="4-网络编程"><a href="#4-网络编程" class="headerlink" title="4. 网络编程"></a>4. 网络编程</h1><h2 id="4-1-非阻塞-vs-阻塞"><a href="#4-1-非阻塞-vs-阻塞" class="headerlink" title="4.1 非阻塞 vs 阻塞"></a>4.1 非阻塞 vs 阻塞</h2><h3 id="阻塞"><a href="#阻塞" class="headerlink" title="阻塞"></a>阻塞</h3><ul>
<li>阻塞模式下，相关方法都会导致线程暂停<ul>
<li>ServerSocketChannel.accept 会在没有连接建立时让线程暂停</li>
<li>SocketChannel.read 会在没有数据可读时让线程暂停</li>
<li>阻塞的表现其实就是线程暂停了，暂停期间不会占用 cpu，但线程相当于闲置</li>
</ul>
</li>
<li>单线程下，阻塞方法之间相互影响，几乎不能正常工作，需要多线程支持</li>
<li>但多线程下，有新的问题，体现在以下方面<ul>
<li>32 位 jvm 一个线程 320k，64 位 jvm 一个线程 1024k，如果连接数过多，必然导致 OOM，并且线程太多，反而会因为频繁上下文切换导致性能降低</li>
<li>可以采用线程池技术来减少线程数和线程上下文切换，但治标不治本，如果有很多连接建立，但长时间 inactive，会阻塞线程池中所有线程，因此不适合长连接，只适合短连接</li>
</ul>
</li>
</ul>
<p>服务器端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用 nio 来理解阻塞模式, 单线程</span></span><br><span class="line"><span class="comment">// 0. ByteBuffer</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br><span class="line"><span class="comment">// 1. 创建了服务器</span></span><br><span class="line"><span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 绑定监听端口</span></span><br><span class="line">ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 连接集合</span></span><br><span class="line">List&lt;SocketChannel&gt; channels = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 4. accept 建立与客户端连接， SocketChannel 用来与客户端之间通信</span></span><br><span class="line">    log.debug(<span class="string">&quot;connecting...&quot;</span>);</span><br><span class="line">    <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> ssc.accept(); <span class="comment">// 阻塞方法，线程停止运行</span></span><br><span class="line">    log.debug(<span class="string">&quot;connected... &#123;&#125;&quot;</span>, sc);</span><br><span class="line">    channels.add(sc);</span><br><span class="line">    <span class="keyword">for</span> (SocketChannel channel : channels) &#123;</span><br><span class="line">        <span class="comment">// 5. 接收客户端发送的数据</span></span><br><span class="line">        log.debug(<span class="string">&quot;before read... &#123;&#125;&quot;</span>, channel);</span><br><span class="line">        channel.read(buffer); <span class="comment">// 阻塞方法，线程停止运行</span></span><br><span class="line">        buffer.flip();</span><br><span class="line">        debugRead(buffer);</span><br><span class="line">        buffer.clear();</span><br><span class="line">        log.debug(<span class="string">&quot;after read...&#123;&#125;&quot;</span>, channel);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">sc.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>));</span><br><span class="line">log.info(<span class="string">&quot;waiting...&quot;</span>);</span><br></pre></td></tr></table></figure>



<h3 id="非阻塞"><a href="#非阻塞" class="headerlink" title="非阻塞"></a>非阻塞</h3><ul>
<li>非阻塞模式下，相关方法都会不会让线程暂停<ul>
<li>在 ServerSocketChannel.accept 在没有连接建立时，会返回 null，继续运行</li>
<li>SocketChannel.read 在没有数据可读时，会返回 0，但线程不必阻塞，可以去执行其它 SocketChannel 的 read 或是去执行 ServerSocketChannel.accept </li>
<li>写数据时，线程只是等待数据写入 Channel 即可，无需等 Channel 通过网络把数据发送出去</li>
</ul>
</li>
<li>但非阻塞模式下，即使没有连接建立，和可读数据，线程仍然在不断运行，白白浪费了 cpu</li>
<li>数据复制过程中，线程实际还是阻塞的（AIO 改进的地方）</li>
</ul>
<p>服务器端，客户端代码不变</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用 nio 来理解非阻塞模式, 单线程</span></span><br><span class="line"><span class="comment">// 0. ByteBuffer</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br><span class="line"><span class="comment">// 1. 创建了服务器</span></span><br><span class="line"><span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">ssc.configureBlocking(<span class="literal">false</span>); <span class="comment">// 非阻塞模式</span></span><br><span class="line"><span class="comment">// 2. 绑定监听端口</span></span><br><span class="line">ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line"><span class="comment">// 3. 连接集合</span></span><br><span class="line">List&lt;SocketChannel&gt; channels = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 4. accept 建立与客户端连接， SocketChannel 用来与客户端之间通信</span></span><br><span class="line">    <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> ssc.accept(); <span class="comment">// 非阻塞，线程还会继续运行，如果没有连接建立，但sc是null</span></span><br><span class="line">    <span class="keyword">if</span> (sc != <span class="literal">null</span>) &#123;</span><br><span class="line">        log.debug(<span class="string">&quot;connected... &#123;&#125;&quot;</span>, sc);</span><br><span class="line">        sc.configureBlocking(<span class="literal">false</span>); <span class="comment">// 非阻塞模式</span></span><br><span class="line">        channels.add(sc);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (SocketChannel channel : channels) &#123;</span><br><span class="line">        <span class="comment">// 5. 接收客户端发送的数据</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> channel.read(buffer);<span class="comment">// 非阻塞，线程仍然会继续运行，如果没有读到数据，read 返回 0</span></span><br><span class="line">        <span class="keyword">if</span> (read &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            buffer.flip();</span><br><span class="line">            debugRead(buffer);</span><br><span class="line">            buffer.clear();</span><br><span class="line">            log.debug(<span class="string">&quot;after read...&#123;&#125;&quot;</span>, channel);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="多路复用"><a href="#多路复用" class="headerlink" title="多路复用"></a>多路复用</h3><p>单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控，这称之为多路复用</p>
<ul>
<li>多路复用仅针对网络 IO、普通文件 IO 没法利用多路复用</li>
<li>如果不用 Selector 的非阻塞模式，线程大部分时间都在做无用功，而 Selector 能够保证<ul>
<li>有可连接事件时才去连接</li>
<li>有可读事件才去读取</li>
<li>有可写事件才去写入</li>
<li>限于网络传输能力，Channel 未必时时可写，一旦 Channel 可写，会触发 Selector 的可写事件</li>
</ul>
</li>
</ul>
<h2 id="4-2-Selector"><a href="#4-2-Selector" class="headerlink" title="4.2 Selector"></a>4.2 Selector</h2><pre class="mermaid">graph TD
subgraph selector 版
thread --> selector
selector --> c1(channel)
selector --> c2(channel)
selector --> c3(channel)
end</pre>



<p>好处</p>
<ul>
<li>一个线程配合 selector 就可以监控多个 channel 的事件，事件发生线程才去处理。避免非阻塞模式下所做无用功</li>
<li>让这个线程能够被充分利用</li>
<li>节约了线程的数量</li>
<li>减少了线程上下文切换</li>
</ul>
<h3 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br></pre></td></tr></table></figure>



<h3 id="绑定-Channel-事件"><a href="#绑定-Channel-事件" class="headerlink" title="绑定 Channel 事件"></a>绑定 Channel 事件</h3><p>也称之为注册事件，绑定的事件 selector 才会关心 </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">channel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line"><span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> channel.register(selector, 绑定事件);</span><br></pre></td></tr></table></figure>

<ul>
<li>channel 必须工作在非阻塞模式</li>
<li>FileChannel 没有非阻塞模式，因此不能配合 selector 一起使用</li>
<li>绑定的事件类型可以有<ul>
<li>connect - 客户端连接成功时触发</li>
<li>accept - 服务器端成功接受连接时触发</li>
<li>read - 数据可读入时触发，有因为接收能力弱，数据暂不能读入的情况</li>
<li>write - 数据可写出时触发，有因为发送能力弱，数据暂不能写出的情况</li>
</ul>
</li>
</ul>
<h3 id="监听-Channel-事件"><a href="#监听-Channel-事件" class="headerlink" title="监听 Channel 事件"></a>监听 Channel 事件</h3><p>可以通过下面三种方法来监听是否有事件发生，方法的返回值代表有多少 channel 发生了事件</p>
<p>方法1，阻塞直到绑定事件发生</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.select();</span><br></pre></td></tr></table></figure>



<p>方法2，阻塞直到绑定事件发生，或是超时（时间单位为 ms）</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.select(<span class="type">long</span> timeout);</span><br></pre></td></tr></table></figure>



<p>方法3，不会阻塞，也就是不管有没有事件，立刻返回，自己根据返回值检查是否有事件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.selectNow();</span><br></pre></td></tr></table></figure>



<h4 id="💡-select-何时不阻塞"><a href="#💡-select-何时不阻塞" class="headerlink" title="💡 select 何时不阻塞"></a>💡 select 何时不阻塞</h4><blockquote>
<ul>
<li>事件发生时<ul>
<li>客户端发起连接请求，会触发 accept 事件</li>
<li>客户端发送数据过来，客户端正常、异常关闭时，都会触发 read 事件，另外如果发送的数据大于 buffer 缓冲区，会触发多次读取事件</li>
<li>channel 可写，会触发 write 事件</li>
<li>在 linux 下 nio bug 发生时</li>
</ul>
</li>
<li>调用 selector.wakeup()</li>
<li>调用 selector.close()</li>
<li>selector 所在线程 interrupt</li>
</ul>
</blockquote>
<h2 id="4-3-处理-accept-事件"><a href="#4-3-处理-accept-事件" class="headerlink" title="4.3 处理 accept 事件"></a>4.3 处理 accept 事件</h2><p>客户端代码为</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Client</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Socket</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>)) &#123;</span><br><span class="line">            System.out.println(socket);</span><br><span class="line">            socket.getOutputStream().write(<span class="string">&quot;world&quot;</span>.getBytes());</span><br><span class="line">            System.in.read();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>服务器端代码为</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChannelDemo6</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">ServerSocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> ServerSocketChannel.open()) &#123;</span><br><span class="line">            channel.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">            System.out.println(channel);</span><br><span class="line">            <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">            channel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">            channel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.select();</span><br><span class="line"><span class="comment">//                int count = selector.selectNow();</span></span><br><span class="line">                log.debug(<span class="string">&quot;select count: &#123;&#125;&quot;</span>, count);</span><br><span class="line"><span class="comment">//                if(count &lt;= 0) &#123;</span></span><br><span class="line"><span class="comment">//                    continue;</span></span><br><span class="line"><span class="comment">//                &#125;</span></span><br><span class="line"></span><br><span class="line">                <span class="comment">// 获取所有事件</span></span><br><span class="line">                Set&lt;SelectionKey&gt; keys = selector.selectedKeys();</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 遍历所有事件，逐一处理</span></span><br><span class="line">                Iterator&lt;SelectionKey&gt; iter = keys.iterator();</span><br><span class="line">                <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                    <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                    <span class="comment">// 判断事件类型</span></span><br><span class="line">                    <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">                        <span class="type">ServerSocketChannel</span> <span class="variable">c</span> <span class="operator">=</span> (ServerSocketChannel) key.channel();</span><br><span class="line">                        <span class="comment">// 必须处理</span></span><br><span class="line">                        <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> c.accept();</span><br><span class="line">                        log.debug(<span class="string">&quot;&#123;&#125;&quot;</span>, sc);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">// 处理完毕，必须将事件移除</span></span><br><span class="line">                    iter.remove();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="💡-事件发生后能否不处理"><a href="#💡-事件发生后能否不处理" class="headerlink" title="💡 事件发生后能否不处理"></a>💡 事件发生后能否不处理</h3><blockquote>
<p>事件发生后，要么处理，要么取消（cancel），不能什么都不做，否则下次该事件仍会触发，这是因为 nio 底层使用的是水平触发</p>
</blockquote>
<h2 id="4-4-处理-read-事件"><a href="#4-4-处理-read-事件" class="headerlink" title="4.4 处理 read 事件"></a>4.4 处理 read 事件</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChannelDemo6</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">ServerSocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> ServerSocketChannel.open()) &#123;</span><br><span class="line">            channel.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">            System.out.println(channel);</span><br><span class="line">            <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">            channel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">            channel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.select();</span><br><span class="line"><span class="comment">//                int count = selector.selectNow();</span></span><br><span class="line">                log.debug(<span class="string">&quot;select count: &#123;&#125;&quot;</span>, count);</span><br><span class="line"><span class="comment">//                if(count &lt;= 0) &#123;</span></span><br><span class="line"><span class="comment">//                    continue;</span></span><br><span class="line"><span class="comment">//                &#125;</span></span><br><span class="line"></span><br><span class="line">                <span class="comment">// 获取所有事件</span></span><br><span class="line">                Set&lt;SelectionKey&gt; keys = selector.selectedKeys();</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 遍历所有事件，逐一处理</span></span><br><span class="line">                Iterator&lt;SelectionKey&gt; iter = keys.iterator();</span><br><span class="line">                <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                    <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                    <span class="comment">// 判断事件类型</span></span><br><span class="line">                    <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">                        <span class="type">ServerSocketChannel</span> <span class="variable">c</span> <span class="operator">=</span> (ServerSocketChannel) key.channel();</span><br><span class="line">                        <span class="comment">// 必须处理</span></span><br><span class="line">                        <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> c.accept();</span><br><span class="line">                        sc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                        sc.register(selector, SelectionKey.OP_READ);</span><br><span class="line">                        log.debug(<span class="string">&quot;连接已建立: &#123;&#125;&quot;</span>, sc);</span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) &#123;</span><br><span class="line">                        <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> (SocketChannel) key.channel();</span><br><span class="line">                        <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">128</span>);</span><br><span class="line">                        <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> sc.read(buffer);</span><br><span class="line">                        <span class="keyword">if</span>(read == -<span class="number">1</span>) &#123;</span><br><span class="line">                            key.cancel();</span><br><span class="line">                            sc.close();</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            buffer.flip();</span><br><span class="line">                            debug(buffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">// 处理完毕，必须将事件移除</span></span><br><span class="line">                    iter.remove();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>开启两个客户端，修改一下发送文字，输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">sun.nio.ch.ServerSocketChannelImpl[/0:0:0:0:0:0:0:0:8080]</span><br><span class="line">21:16:39 [DEBUG] [main] c.i.n.ChannelDemo6 - select count: 1</span><br><span class="line">21:16:39 [DEBUG] [main] c.i.n.ChannelDemo6 - 连接已建立: java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:60367]</span><br><span class="line">21:16:39 [DEBUG] [main] c.i.n.ChannelDemo6 - select count: 1</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 68 65 6c 6c 6f                                  |hello           |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">21:16:59 [DEBUG] [main] c.i.n.ChannelDemo6 - select count: 1</span><br><span class="line">21:16:59 [DEBUG] [main] c.i.n.ChannelDemo6 - 连接已建立: java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:60378]</span><br><span class="line">21:16:59 [DEBUG] [main] c.i.n.ChannelDemo6 - select count: 1</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 77 6f 72 6c 64                                  |world           |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br></pre></td></tr></table></figure>



<h3 id="💡-为何要-iter-remove"><a href="#💡-为何要-iter-remove" class="headerlink" title="💡 为何要 iter.remove()"></a>💡 为何要 iter.remove()</h3><blockquote>
<p>因为 select 在事件发生后，就会将相关的 key 放入 selectedKeys 集合，但不会在处理完后从 selectedKeys 集合中移除，需要我们自己编码删除。例如</p>
<ul>
<li>第一次触发了 ssckey 上的 accept 事件，没有移除 ssckey </li>
<li>第二次触发了 sckey 上的 read 事件，但这时 selectedKeys 中还有上次的 ssckey ，在处理时因为没有真正的 serverSocket 连上了，就会导致空指针异常</li>
</ul>
</blockquote>
<h3 id="💡-cancel-的作用"><a href="#💡-cancel-的作用" class="headerlink" title="💡 cancel 的作用"></a>💡 cancel 的作用</h3><blockquote>
<p>cancel 会取消注册在 selector 上的 channel，并从 keys 集合中删除 key 后续不会再监听事件</p>
</blockquote>
<h3 id="⚠️-不处理边界的问题"><a href="#⚠️-不处理边界的问题" class="headerlink" title="⚠️  不处理边界的问题"></a>⚠️  不处理边界的问题</h3><p>以前有同学写过这样的代码，思考注释中两个问题，以 bio 为例，其实 nio 道理是一样的</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Server</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        ServerSocket ss=<span class="keyword">new</span> <span class="title class_">ServerSocket</span>(<span class="number">9000</span>);</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="type">Socket</span> <span class="variable">s</span> <span class="operator">=</span> ss.accept();</span><br><span class="line">            <span class="type">InputStream</span> <span class="variable">in</span> <span class="operator">=</span> s.getInputStream();</span><br><span class="line">            <span class="comment">// 这里这么写，有没有问题</span></span><br><span class="line">            <span class="type">byte</span>[] arr = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">4</span>];</span><br><span class="line">            <span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> in.read(arr);</span><br><span class="line">                <span class="comment">// 这里这么写，有没有问题</span></span><br><span class="line">                <span class="keyword">if</span>(read == -<span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(arr, <span class="number">0</span>, read));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Client</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">Socket</span> <span class="variable">max</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Socket</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">9000</span>);</span><br><span class="line">        <span class="type">OutputStream</span> <span class="variable">out</span> <span class="operator">=</span> max.getOutputStream();</span><br><span class="line">        out.write(<span class="string">&quot;hello&quot;</span>.getBytes());</span><br><span class="line">        out.write(<span class="string">&quot;world&quot;</span>.getBytes());</span><br><span class="line">        out.write(<span class="string">&quot;你好&quot;</span>.getBytes());</span><br><span class="line">        max.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">hell   <span class="comment"># 服务器先读取4个字节存放在4个字节的byte[]中，然后转化为字符串就是hell</span></span><br><span class="line">owor   <span class="comment"># 服务器再读取4个字节存放在4个字节的byte[]中，然后转化为字符串就是owor</span></span><br><span class="line">ld�   <span class="comment"># 服务器再读取4个字节存放在4个字节的byte[]中，world剩下的部分(ld)+你汉字的前两个字节(乱码),中文可能会占三个字节</span></span><br><span class="line">�好   <span class="comment"># 服务器再读取4个字节存放在4个字节的byte[]中，你汉字的最后1个字节(乱码)+中文好(三个字节)</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>为什么？</p>
<h3 id="处理消息的边界"><a href="#处理消息的边界" class="headerlink" title="处理消息的边界"></a>处理消息的边界</h3><p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161739.png"></p>
<ul>
<li>一种思路是固定消息长度，数据包大小一样，服务器按预定长度读取，缺点是浪费带宽</li>
<li>另一种思路是按分隔符拆分，缺点是效率低</li>
<li>TLV 格式，即 Type 类型、Length 长度、Value 数据，类型和长度已知的情况下，就可以方便获取消息大小，分配合适的 buffer，缺点是 buffer 需要提前分配，如果内容过大，则影响 server 吞吐量<ul>
<li>Http 1.1 是 TLV 格式</li>
<li>Http 2.0 是 LTV 格式</li>
</ul>
</li>
</ul>
<pre class="mermaid">sequenceDiagram 
participant c1 as 客户端1
participant s as 服务器
participant b1 as ByteBuffer1
participant b2 as ByteBuffer2
c1 ->> s: 发送 01234567890abcdef3333\r
s ->> b1: 第一次 read 存入 01234567890abcdef
s ->> b2: 扩容
b1 ->> b2: 拷贝 01234567890abcdef
s ->> b2: 第二次 read 存入 3333\r
b2 ->> b2: 01234567890abcdef3333\r</pre>

<p>服务器端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">split</span><span class="params">(ByteBuffer source)</span> &#123;</span><br><span class="line">    source.flip();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; source.limit(); i++) &#123;</span><br><span class="line">        <span class="comment">// 找到一条完整消息</span></span><br><span class="line">        <span class="keyword">if</span> (source.get(i) == <span class="string">&#x27;\n&#x27;</span>) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> i + <span class="number">1</span> - source.position();</span><br><span class="line">            <span class="comment">// 把这条完整消息存入新的 ByteBuffer</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">target</span> <span class="operator">=</span> ByteBuffer.allocate(length);</span><br><span class="line">            <span class="comment">// 从 source 读，向 target 写</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; length; j++) &#123;</span><br><span class="line">                target.put(source.get());</span><br><span class="line">            &#125;</span><br><span class="line">            debugAll(target);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    source.compact(); <span class="comment">// 0123456789abcdef  position 16 limit 16</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">// 1. 创建 selector, 管理多个 channel</span></span><br><span class="line">    <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">    <span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">    ssc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">    <span class="comment">// 2. 建立 selector 和 channel 的联系（注册）</span></span><br><span class="line">    <span class="comment">// SelectionKey 就是将来事件发生后，通过它可以知道事件和哪个channel的事件</span></span><br><span class="line">    <span class="type">SelectionKey</span> <span class="variable">sscKey</span> <span class="operator">=</span> ssc.register(selector, <span class="number">0</span>, <span class="literal">null</span>);</span><br><span class="line">    <span class="comment">// key 只关注 accept 事件</span></span><br><span class="line">    sscKey.interestOps(SelectionKey.OP_ACCEPT);</span><br><span class="line">    log.debug(<span class="string">&quot;sscKey:&#123;&#125;&quot;</span>, sscKey);</span><br><span class="line">    ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="comment">// 3. select 方法, 没有事件发生，线程阻塞，有事件，线程才会恢复运行</span></span><br><span class="line">        <span class="comment">// select 在事件未处理时，它不会阻塞, 事件发生后要么处理，要么取消，不能置之不理</span></span><br><span class="line">        selector.select();</span><br><span class="line">        <span class="comment">// 4. 处理事件, selectedKeys 内部包含了所有发生的事件</span></span><br><span class="line">        Iterator&lt;SelectionKey&gt; iter = selector.selectedKeys().iterator(); <span class="comment">// accept, read</span></span><br><span class="line">        <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">            <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">            <span class="comment">// 处理key 时，要从 selectedKeys 集合中删除，否则下次处理就会有问题</span></span><br><span class="line">            iter.remove();</span><br><span class="line">            log.debug(<span class="string">&quot;key: &#123;&#125;&quot;</span>, key);</span><br><span class="line">            <span class="comment">// 5. 区分事件类型</span></span><br><span class="line">            <span class="keyword">if</span> (key.isAcceptable()) &#123; <span class="comment">// 如果是 accept</span></span><br><span class="line">                <span class="type">ServerSocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> (ServerSocketChannel) key.channel();</span><br><span class="line">                <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> channel.accept();</span><br><span class="line">                sc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>); <span class="comment">// attachment</span></span><br><span class="line">                <span class="comment">// 将一个 byteBuffer 作为附件关联到 selectionKey 上</span></span><br><span class="line">                <span class="type">SelectionKey</span> <span class="variable">scKey</span> <span class="operator">=</span> sc.register(selector, <span class="number">0</span>, buffer);</span><br><span class="line">                scKey.interestOps(SelectionKey.OP_READ);</span><br><span class="line">                log.debug(<span class="string">&quot;&#123;&#125;&quot;</span>, sc);</span><br><span class="line">                log.debug(<span class="string">&quot;scKey:&#123;&#125;&quot;</span>, scKey);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) &#123; <span class="comment">// 如果是 read</span></span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="type">SocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> (SocketChannel) key.channel(); <span class="comment">// 拿到触发事件的channel</span></span><br><span class="line">                    <span class="comment">// 获取 selectionKey 上关联的附件</span></span><br><span class="line">                    <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> (ByteBuffer) key.attachment();</span><br><span class="line">                    <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> channel.read(buffer); <span class="comment">// 如果是正常断开，read 的方法的返回值是 -1</span></span><br><span class="line">                    <span class="keyword">if</span>(read == -<span class="number">1</span>) &#123;</span><br><span class="line">                        key.cancel();</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        split(buffer);</span><br><span class="line">                        <span class="comment">// 需要扩容</span></span><br><span class="line">                        <span class="keyword">if</span> (buffer.position() == buffer.limit()) &#123;</span><br><span class="line">                            <span class="type">ByteBuffer</span> <span class="variable">newBuffer</span> <span class="operator">=</span> ByteBuffer.allocate(buffer.capacity() * <span class="number">2</span>);</span><br><span class="line">                            buffer.flip();</span><br><span class="line">                            newBuffer.put(buffer); <span class="comment">// 0123456789abcdef3333\n</span></span><br><span class="line">                            key.attach(newBuffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                    key.cancel();  <span class="comment">// 因为客户端断开了,因此需要将 key 取消（从 selector 的 keys 集合中真正删除 key）</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">sc.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>));</span><br><span class="line"><span class="type">SocketAddress</span> <span class="variable">address</span> <span class="operator">=</span> sc.getLocalAddress();</span><br><span class="line"><span class="comment">// sc.write(Charset.defaultCharset().encode(&quot;hello\nworld\n&quot;));</span></span><br><span class="line">sc.write(Charset.defaultCharset().encode(<span class="string">&quot;0123\n456789abcdef&quot;</span>));</span><br><span class="line">sc.write(Charset.defaultCharset().encode(<span class="string">&quot;0123456789abcdef3333\n&quot;</span>));</span><br><span class="line">System.in.read();</span><br></pre></td></tr></table></figure>





<h3 id="ByteBuffer-大小分配"><a href="#ByteBuffer-大小分配" class="headerlink" title="ByteBuffer 大小分配"></a>ByteBuffer 大小分配</h3><ul>
<li>每个 channel 都需要记录可能被切分的消息，因为 ByteBuffer 不能被多个 channel 共同使用，因此需要为每个 channel 维护一个独立的 ByteBuffer</li>
<li>ByteBuffer 不能太大，比如一个 ByteBuffer 1Mb 的话，要支持百万连接就要 1Tb 内存，因此需要设计大小可变的 ByteBuffer<ul>
<li>一种思路是首先分配一个较小的 buffer，例如 4k，如果发现数据不够，再分配 8k 的 buffer，将 4k buffer 内容拷贝至 8k buffer，优点是消息连续容易处理，缺点是数据拷贝耗费性能，参考实现 <a target="_blank" rel="noopener" href="http://tutorials.jenkov.com/java-performance/resizable-array.html">http://tutorials.jenkov.com/java-performance/resizable-array.html</a></li>
<li>另一种思路是用多个数组组成 buffer，一个数组不够，把多出来的内容写入新的数组，与前面的区别是消息存储不连续解析复杂，优点是避免了拷贝引起的性能损耗</li>
</ul>
</li>
</ul>
<h2 id="4-5-处理-write-事件"><a href="#4-5-处理-write-事件" class="headerlink" title="4.5 处理 write 事件"></a>4.5 处理 write 事件</h2><h3 id="一次无法写完例子"><a href="#一次无法写完例子" class="headerlink" title="一次无法写完例子"></a>一次无法写完例子</h3><ul>
<li>非阻塞模式下，无法保证把 buffer 中所有数据都写入 channel，因此需要追踪 write 方法的返回值（代表实际写入字节数）</li>
<li>用 selector 监听所有 channel 的可写事件，每个 channel 都需要一个 key 来跟踪 buffer，但这样又会导致占用内存过多，就有两阶段策略<ul>
<li>当消息处理器第一次写入消息时，才将 channel 注册到 selector 上</li>
<li>selector 检查 channel 上的可写事件，如果所有的数据写完了，就取消 channel 的注册</li>
<li>如果不取消，会每次可写均会触发 write 事件</li>
</ul>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WriteServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">        ssc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">        ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line"></span><br><span class="line">        <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">        ssc.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">            selector.select();</span><br><span class="line"></span><br><span class="line">            Iterator&lt;SelectionKey&gt; iter = selector.selectedKeys().iterator();</span><br><span class="line">            <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                iter.remove();</span><br><span class="line">                <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">                    <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> ssc.accept();</span><br><span class="line">                    sc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                    <span class="type">SelectionKey</span> <span class="variable">sckey</span> <span class="operator">=</span> sc.register(selector, SelectionKey.OP_READ);</span><br><span class="line">                    <span class="comment">// 1. 向客户端发送内容</span></span><br><span class="line">                    <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">                    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">3000000</span>; i++) &#123;</span><br><span class="line">                        sb.append(<span class="string">&quot;a&quot;</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> Charset.defaultCharset().encode(sb.toString());</span><br><span class="line">                    <span class="type">int</span> <span class="variable">write</span> <span class="operator">=</span> sc.write(buffer);</span><br><span class="line">                    <span class="comment">// 3. write 表示实际写了多少字节</span></span><br><span class="line">                    System.out.println(<span class="string">&quot;实际写入字节:&quot;</span> + write);</span><br><span class="line">                    <span class="comment">// 4. 如果有剩余未读字节，才需要关注写事件</span></span><br><span class="line">                    <span class="keyword">if</span> (buffer.hasRemaining()) &#123;</span><br><span class="line">                        <span class="comment">// read 1  write 4</span></span><br><span class="line">                        <span class="comment">// 在原有关注事件的基础上，多关注 写事件</span></span><br><span class="line">                        sckey.interestOps(sckey.interestOps() + SelectionKey.OP_WRITE);</span><br><span class="line">                        <span class="comment">// 把 buffer 作为附件加入 sckey</span></span><br><span class="line">                        sckey.attach(buffer);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isWritable()) &#123;</span><br><span class="line">                    <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> (ByteBuffer) key.attachment();</span><br><span class="line">                    <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> (SocketChannel) key.channel();</span><br><span class="line">                    <span class="type">int</span> <span class="variable">write</span> <span class="operator">=</span> sc.write(buffer);</span><br><span class="line">                    System.out.println(<span class="string">&quot;实际写入字节:&quot;</span> + write);</span><br><span class="line">                    <span class="keyword">if</span> (!buffer.hasRemaining()) &#123; <span class="comment">// 写完了</span></span><br><span class="line">                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);</span><br><span class="line">                        key.attach(<span class="literal">null</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WriteClient</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">        <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">        sc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">        sc.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ);</span><br><span class="line">        sc.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>));</span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            selector.select();</span><br><span class="line">            Iterator&lt;SelectionKey&gt; iter = selector.selectedKeys().iterator();</span><br><span class="line">            <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                iter.remove();</span><br><span class="line">                <span class="keyword">if</span> (key.isConnectable()) &#123;</span><br><span class="line">                    System.out.println(sc.finishConnect());</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) &#123;</span><br><span class="line">                    <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span> * <span class="number">1024</span>);</span><br><span class="line">                    count += sc.read(buffer);</span><br><span class="line">                    buffer.clear();</span><br><span class="line">                    System.out.println(count);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="💡-write-为何要取消"><a href="#💡-write-为何要取消" class="headerlink" title="💡 write 为何要取消"></a>💡 write 为何要取消</h3><p>只要向 channel 发送数据时，socket 缓冲可写，这个事件会频繁触发，因此应当只在 socket 缓冲区写不下时再关注可写事件，数据写完之后再取消关注</p>
<h2 id="4-6-更进一步"><a href="#4-6-更进一步" class="headerlink" title="4.6 更进一步"></a>4.6 更进一步</h2><h3 id="💡-利用多线程优化"><a href="#💡-利用多线程优化" class="headerlink" title="💡 利用多线程优化"></a>💡 利用多线程优化</h3><blockquote>
<p>现在都是多核 cpu，设计时要充分考虑别让 cpu 的力量被白白浪费</p>
</blockquote>
<p>前面的代码只有一个选择器，没有充分利用多核 cpu，如何改进呢？</p>
<p>分两组选择器</p>
<ul>
<li>单线程配一个选择器，专门处理 accept 事件</li>
<li>创建 cpu 核心数的线程，每个线程配一个选择器，轮流处理 read 事件</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChannelDemo7</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">BossEventLoop</span>().register();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Slf4j</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">BossEventLoop</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> Selector boss;</span><br><span class="line">        <span class="keyword">private</span> WorkerEventLoop[] workers;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">start</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">AtomicInteger</span> <span class="variable">index</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">register</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">            <span class="keyword">if</span> (!start) &#123;</span><br><span class="line">                <span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">                ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">                ssc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                boss = Selector.open();</span><br><span class="line">                <span class="type">SelectionKey</span> <span class="variable">ssckey</span> <span class="operator">=</span> ssc.register(boss, <span class="number">0</span>, <span class="literal">null</span>);</span><br><span class="line">                ssckey.interestOps(SelectionKey.OP_ACCEPT);</span><br><span class="line">                workers = initEventLoops();</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="built_in">this</span>, <span class="string">&quot;boss&quot;</span>).start();</span><br><span class="line">                log.debug(<span class="string">&quot;boss start...&quot;</span>);</span><br><span class="line">                start = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> WorkerEventLoop[] initEventLoops() &#123;</span><br><span class="line"><span class="comment">//        EventLoop[] eventLoops = new EventLoop[Runtime.getRuntime().availableProcessors()];</span></span><br><span class="line">            WorkerEventLoop[] workerEventLoops = <span class="keyword">new</span> <span class="title class_">WorkerEventLoop</span>[<span class="number">2</span>];</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; workerEventLoops.length; i++) &#123;</span><br><span class="line">                workerEventLoops[i] = <span class="keyword">new</span> <span class="title class_">WorkerEventLoop</span>(i);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> workerEventLoops;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    boss.select();</span><br><span class="line">                    Iterator&lt;SelectionKey&gt; iter = boss.selectedKeys().iterator();</span><br><span class="line">                    <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                        <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                        iter.remove();</span><br><span class="line">                        <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">                            <span class="type">ServerSocketChannel</span> <span class="variable">c</span> <span class="operator">=</span> (ServerSocketChannel) key.channel();</span><br><span class="line">                            <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> c.accept();</span><br><span class="line">                            sc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                            log.debug(<span class="string">&quot;&#123;&#125; connected&quot;</span>, sc.getRemoteAddress());</span><br><span class="line">                            workers[index.getAndIncrement() % workers.length].register(sc);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Slf4j</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">WorkerEventLoop</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> Selector worker;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">start</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> index;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> ConcurrentLinkedQueue&lt;Runnable&gt; tasks = <span class="keyword">new</span> <span class="title class_">ConcurrentLinkedQueue</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">WorkerEventLoop</span><span class="params">(<span class="type">int</span> index)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.index = index;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(SocketChannel sc)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">            <span class="keyword">if</span> (!start) &#123;</span><br><span class="line">                worker = Selector.open();</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="built_in">this</span>, <span class="string">&quot;worker-&quot;</span> + index).start();</span><br><span class="line">                start = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            tasks.add(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="type">SelectionKey</span> <span class="variable">sckey</span> <span class="operator">=</span> sc.register(worker, <span class="number">0</span>, <span class="literal">null</span>);</span><br><span class="line">                    sckey.interestOps(SelectionKey.OP_READ);</span><br><span class="line">                    worker.selectNow();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            worker.wakeup();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    worker.select();</span><br><span class="line">                    <span class="type">Runnable</span> <span class="variable">task</span> <span class="operator">=</span> tasks.poll();</span><br><span class="line">                    <span class="keyword">if</span> (task != <span class="literal">null</span>) &#123;</span><br><span class="line">                        task.run();</span><br><span class="line">                    &#125;</span><br><span class="line">                    Set&lt;SelectionKey&gt; keys = worker.selectedKeys();</span><br><span class="line">                    Iterator&lt;SelectionKey&gt; iter = keys.iterator();</span><br><span class="line">                    <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                        <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                        <span class="keyword">if</span> (key.isReadable()) &#123;</span><br><span class="line">                            <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> (SocketChannel) key.channel();</span><br><span class="line">                            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">128</span>);</span><br><span class="line">                            <span class="keyword">try</span> &#123;</span><br><span class="line">                                <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> sc.read(buffer);</span><br><span class="line">                                <span class="keyword">if</span> (read == -<span class="number">1</span>) &#123;</span><br><span class="line">                                    key.cancel();</span><br><span class="line">                                    sc.close();</span><br><span class="line">                                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                    buffer.flip();</span><br><span class="line">                                    log.debug(<span class="string">&quot;&#123;&#125; message:&quot;</span>, sc.getRemoteAddress());</span><br><span class="line">                                    debugAll(buffer);</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                                e.printStackTrace();</span><br><span class="line">                                key.cancel();</span><br><span class="line">                                sc.close();</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                        iter.remove();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="💡-如何拿到-cpu-个数"><a href="#💡-如何拿到-cpu-个数" class="headerlink" title="💡 如何拿到 cpu 个数"></a>💡 如何拿到 cpu 个数</h3><blockquote>
<ul>
<li>Runtime.getRuntime().availableProcessors() 如果工作在 docker 容器下，因为容器不是物理隔离的，会拿到物理 cpu 个数，而不是容器申请时的个数</li>
<li>这个问题直到 jdk 10 才修复，使用 jvm 参数 UseContainerSupport 配置， 默认开启</li>
</ul>
</blockquote>
<h2 id="4-7-UDP"><a href="#4-7-UDP" class="headerlink" title="4.7 UDP"></a>4.7 UDP</h2><ul>
<li>UDP 是无连接的，client 发送数据不会管 server 是否开启</li>
<li>server 这边的 receive 方法会将接收到的数据存入 byte buffer，但如果数据报文超过 buffer 大小，多出来的数据会被默默抛弃</li>
</ul>
<p>首先启动服务器端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UdpServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">DatagramChannel</span> <span class="variable">channel</span> <span class="operator">=</span> DatagramChannel.open()) &#123;</span><br><span class="line">            channel.socket().bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">9999</span>));</span><br><span class="line">            System.out.println(<span class="string">&quot;waiting...&quot;</span>);</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">32</span>);</span><br><span class="line">            channel.receive(buffer);</span><br><span class="line">            buffer.flip();</span><br><span class="line">            debug(buffer);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">waiting...</span><br></pre></td></tr></table></figure>



<p>运行客户端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UdpClient</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">DatagramChannel</span> <span class="variable">channel</span> <span class="operator">=</span> DatagramChannel.open()) &#123;</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> StandardCharsets.UTF_8.encode(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line">            <span class="type">InetSocketAddress</span> <span class="variable">address</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">9999</span>);</span><br><span class="line">            channel.send(buffer, address);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接下来服务器端输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 68 65 6c 6c 6f                                  |hello           |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br></pre></td></tr></table></figure>

<h1 id="5-NIO-vs-BIO"><a href="#5-NIO-vs-BIO" class="headerlink" title="5. NIO vs BIO"></a>5. NIO vs BIO</h1><h2 id="5-1-stream-vs-channel"><a href="#5-1-stream-vs-channel" class="headerlink" title="5.1 stream vs channel"></a>5.1 stream vs channel</h2><ul>
<li>stream 不会自动缓冲数据，channel 会利用系统提供的发送缓冲区、接收缓冲区（更为底层）</li>
<li>stream 仅支持阻塞 API，channel 同时支持阻塞、非阻塞 API，网络 channel 可配合 selector 实现多路复用</li>
<li>二者均为全双工，即读写可以同时进行</li>
</ul>
<h2 id="5-2-IO-模型"><a href="#5-2-IO-模型" class="headerlink" title="5.2 IO 模型"></a>5.2 IO 模型</h2><p>同步阻塞、同步非阻塞、同步多路复用、异步阻塞（没有此情况）、异步非阻塞</p>
<ul>
<li>同步：线程自己去获取结果（同一个线程）</li>
<li>异步：线程自己不去获取结果，而是由其它线程送结果（至少两个线程）</li>
</ul>
<p>当调用一次 channel.read 或 stream.read 后，会切换至操作系统内核态来完成真正数据读取，而读取又分为两个阶段，分别为：</p>
<ul>
<li>等待数据阶段</li>
<li>复制数据阶段</li>
</ul>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804160927.png"></p>
<ul>
<li><p>阻塞 IO</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161002.png" alt="阻塞IO"></p>
</li>
<li><p>非阻塞  IO</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161029.png" alt="非阻塞IO"></p>
</li>
<li><p>多路复用</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161056.png" alt="多路复用"></p>
</li>
<li><p>信号驱动</p>
</li>
<li><p>异步 IO</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161109.png" alt="异步 IO"></p>
</li>
<li><p>阻塞 IO vs 多路复用</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161131.png"></p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161143.png"></p>
</li>
</ul>
<h4 id="🔖-参考"><a href="#🔖-参考" class="headerlink" title="🔖 参考"></a>🔖 参考</h4><p>UNIX 网络编程 - 卷 I</p>
<h2 id="5-3-零拷贝"><a href="#5-3-零拷贝" class="headerlink" title="5.3 零拷贝"></a>5.3 零拷贝</h2><h3 id="传统-IO-问题"><a href="#传统-IO-问题" class="headerlink" title="传统 IO 问题"></a>传统 IO 问题</h3><p>传统的 IO 将一个文件通过 socket 写出</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">File</span> <span class="variable">f</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line"><span class="type">RandomAccessFile</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(file, <span class="string">&quot;r&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">byte</span>[] buf = <span class="keyword">new</span> <span class="title class_">byte</span>[(<span class="type">int</span>)f.length()];</span><br><span class="line">file.read(buf);</span><br><span class="line"></span><br><span class="line"><span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> ...;</span><br><span class="line">socket.getOutputStream().write(buf);</span><br></pre></td></tr></table></figure>

<p>内部工作流程是这样的：</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161519.png"></p>
<ol>
<li><p>java 本身并不具备 IO 读写能力，因此 read 方法调用后，要从 java 程序的<strong>用户态</strong>切换至<strong>内核态</strong>，去调用操作系统（Kernel）的读能力，将数据读入<strong>内核缓冲区</strong>。这期间用户线程阻塞，操作系统使用 DMA（Direct Memory Access）来实现文件读，其间也不会使用 cpu</p>
<blockquote>
<p>DMA 也可以理解为硬件单元，用来解放 cpu 完成文件 IO</p>
</blockquote>
</li>
<li><p>从<strong>内核态</strong>切换回<strong>用户态</strong>，将数据从<strong>内核缓冲区</strong>读入<strong>用户缓冲区</strong>（即 byte[] buf），这期间 cpu 会参与拷贝，无法利用 DMA</p>
</li>
<li><p>调用 write 方法，这时将数据从<strong>用户缓冲区</strong>（byte[] buf）写入 <strong>socket 缓冲区</strong>，cpu 会参与拷贝</p>
</li>
<li><p>接下来要向网卡写数据，这项能力 java 又不具备，因此又得从<strong>用户态</strong>切换至<strong>内核态</strong>，调用操作系统的写能力，使用 DMA 将 <strong>socket 缓冲区</strong>的数据写入网卡，不会使用 cpu</p>
</li>
</ol>
<p>可以看到中间环节较多，java 的 IO 实际不是物理设备级别的读写，而是缓存的复制，底层的真正读写是操作系统来完成的</p>
<ul>
<li>用户态与内核态的切换发生了 3 次，这个操作比较重量级</li>
<li>数据拷贝了共 4 次</li>
</ul>
<h4 id="NIO-优化"><a href="#NIO-优化" class="headerlink" title="NIO 优化"></a>NIO 优化</h4><p>通过 DirectByteBuf </p>
<ul>
<li>ByteBuffer.allocate(10)  HeapByteBuffer 使用的还是 java 内存</li>
<li>ByteBuffer.allocateDirect(10)  DirectByteBuffer 使用的是操作系统内存</li>
</ul>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161528.png"></p>
<p>大部分步骤与优化前相同，不再赘述。唯有一点：java 可以使用 DirectByteBuf 将堆外内存映射到 jvm 内存中来直接访问使用</p>
<ul>
<li>这块内存不受 jvm 垃圾回收的影响，因此内存地址固定，有助于 IO 读写</li>
<li>java 中的 DirectByteBuf 对象仅维护了此内存的虚引用，内存回收分成两步<ul>
<li>DirectByteBuf 对象被垃圾回收，将虚引用加入引用队列</li>
<li>通过专门线程访问引用队列，根据虚引用释放堆外内存</li>
</ul>
</li>
<li>减少了一次数据拷贝，用户态与内核态的切换次数没有减少</li>
</ul>
<p>进一步优化（底层采用了 linux 2.1 后提供的 sendFile 方法），java 中对应着两个 channel 调用 transferTo/transferFrom 方法拷贝数据</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161536.png"></p>
<ol>
<li>java 调用 transferTo 方法后，要从 java 程序的<strong>用户态</strong>切换至<strong>内核态</strong>，使用 DMA将数据读入<strong>内核缓冲区</strong>，不会使用 cpu</li>
<li>数据从<strong>内核缓冲区</strong>传输到 <strong>socket 缓冲区</strong>，cpu 会参与拷贝</li>
<li>最后使用 DMA 将 <strong>socket 缓冲区</strong>的数据写入网卡，不会使用 cpu</li>
</ol>
<p>可以看到</p>
<ul>
<li>只发生了一次用户态与内核态的切换</li>
<li>数据拷贝了 3 次</li>
</ul>
<p>进一步优化（linux 2.4）</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210804161606.png"></p>
<ol>
<li>java 调用 transferTo 方法后，要从 java 程序的<strong>用户态</strong>切换至<strong>内核态</strong>，使用 DMA将数据读入<strong>内核缓冲区</strong>，不会使用 cpu</li>
<li>只会将一些 offset 和 length 信息拷入 <strong>socket 缓冲区</strong>，几乎无消耗</li>
<li>使用 DMA 将 <strong>内核缓冲区</strong>的数据写入网卡，不会使用 cpu</li>
</ol>
<p>整个过程仅只发生了一次用户态与内核态的切换，数据拷贝了 2 次。所谓的【零拷贝】，并不是真正无拷贝，而是在不会拷贝重复数据到 jvm 内存中，零拷贝的优点有</p>
<ul>
<li>更少的用户态与内核态的切换</li>
<li>不利用 cpu 计算，减少 cpu 缓存伪共享</li>
<li>零拷贝适合小文件传输</li>
</ul>
<h2 id="5-3-AIO"><a href="#5-3-AIO" class="headerlink" title="5.3 AIO"></a>5.3 AIO</h2><p>AIO 用来解决数据复制阶段的阻塞问题</p>
<ul>
<li>同步意味着，在进行读写操作时，线程需要等待结果，还是相当于闲置</li>
<li>异步意味着，在进行读写操作时，线程不必等待结果，而是将来由操作系统来通过回调方式由另外的线程来获得结果</li>
</ul>
<blockquote>
<p>异步模型需要底层操作系统（Kernel）提供支持</p>
<ul>
<li>Windows 系统通过 IOCP 实现了真正的异步 IO</li>
<li>Linux 系统异步 IO 在 2.6 版本引入，但其底层实现还是用多路复用模拟了异步 IO，性能没有优势</li>
</ul>
</blockquote>
<h3 id="文件-AIO"><a href="#文件-AIO" class="headerlink" title="文件 AIO"></a>文件 AIO</h3><p>先来看看 AsynchronousFileChannel</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AioDemo1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="type">AsynchronousFileChannel</span> <span class="variable">s</span> <span class="operator">=</span> </span><br><span class="line">                AsynchronousFileChannel.open(</span><br><span class="line">                	Paths.get(<span class="string">&quot;1.txt&quot;</span>), StandardOpenOption.READ);</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">2</span>);</span><br><span class="line">            log.debug(<span class="string">&quot;begin...&quot;</span>);</span><br><span class="line">            s.read(buffer, <span class="number">0</span>, <span class="literal">null</span>, <span class="keyword">new</span> <span class="title class_">CompletionHandler</span>&lt;Integer, ByteBuffer&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completed</span><span class="params">(Integer result, ByteBuffer attachment)</span> &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;read completed...&#123;&#125;&quot;</span>, result);</span><br><span class="line">                    buffer.flip();</span><br><span class="line">                    debug(buffer);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;read failed...&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        log.debug(<span class="string">&quot;do other things...&quot;</span>);</span><br><span class="line">        System.in.read();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">13:44:56 [DEBUG] [main] c.i.aio.AioDemo1 - begin...</span><br><span class="line">13:44:56 [DEBUG] [main] c.i.aio.AioDemo1 - do other things...</span><br><span class="line">13:44:56 [DEBUG] [Thread-5] c.i.aio.AioDemo1 - read completed...2</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 0d                                           |a.              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br></pre></td></tr></table></figure>

<p>可以看到</p>
<ul>
<li>响应文件读取成功的是另一个线程 Thread-5</li>
<li>主线程并没有 IO 操作阻塞</li>
</ul>
<h4 id="💡-守护线程"><a href="#💡-守护线程" class="headerlink" title="💡 守护线程"></a>💡 守护线程</h4><p>默认文件 AIO 使用的线程都是守护线程，所以最后要执行 <code>System.in.read()</code> 以避免守护线程意外结束</p>
<h3 id="网络-AIO"><a href="#网络-AIO" class="headerlink" title="网络 AIO"></a>网络 AIO</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AioServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">AsynchronousServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> AsynchronousServerSocketChannel.open();</span><br><span class="line">        ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">        ssc.accept(<span class="literal">null</span>, <span class="keyword">new</span> <span class="title class_">AcceptHandler</span>(ssc));</span><br><span class="line">        System.in.read();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">closeChannel</span><span class="params">(AsynchronousSocketChannel sc)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            System.out.printf(<span class="string">&quot;[%s] %s close\n&quot;</span>, Thread.currentThread().getName(), sc.getRemoteAddress());</span><br><span class="line">            sc.close();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ReadHandler</span> <span class="keyword">implements</span> <span class="title class_">CompletionHandler</span>&lt;Integer, ByteBuffer&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> AsynchronousSocketChannel sc;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">ReadHandler</span><span class="params">(AsynchronousSocketChannel sc)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.sc = sc;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completed</span><span class="params">(Integer result, ByteBuffer attachment)</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (result == -<span class="number">1</span>) &#123;</span><br><span class="line">                    closeChannel(sc);</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                System.out.printf(<span class="string">&quot;[%s] %s read\n&quot;</span>, Thread.currentThread().getName(), sc.getRemoteAddress());</span><br><span class="line">                attachment.flip();</span><br><span class="line">                System.out.println(Charset.defaultCharset().decode(attachment));</span><br><span class="line">                attachment.clear();</span><br><span class="line">                <span class="comment">// 处理完第一个 read 时，需要再次调用 read 方法来处理下一个 read 事件</span></span><br><span class="line">                sc.read(attachment, attachment, <span class="built_in">this</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> &#123;</span><br><span class="line">            closeChannel(sc);</span><br><span class="line">            exc.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">WriteHandler</span> <span class="keyword">implements</span> <span class="title class_">CompletionHandler</span>&lt;Integer, ByteBuffer&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> AsynchronousSocketChannel sc;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="title function_">WriteHandler</span><span class="params">(AsynchronousSocketChannel sc)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.sc = sc;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completed</span><span class="params">(Integer result, ByteBuffer attachment)</span> &#123;</span><br><span class="line">            <span class="comment">// 如果作为附件的 buffer 还有内容，需要再次 write 写出剩余内容</span></span><br><span class="line">            <span class="keyword">if</span> (attachment.hasRemaining()) &#123;</span><br><span class="line">                sc.write(attachment);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> &#123;</span><br><span class="line">            exc.printStackTrace();</span><br><span class="line">            closeChannel(sc);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">AcceptHandler</span> <span class="keyword">implements</span> <span class="title class_">CompletionHandler</span>&lt;AsynchronousSocketChannel, Object&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> AsynchronousServerSocketChannel ssc;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">AcceptHandler</span><span class="params">(AsynchronousServerSocketChannel ssc)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.ssc = ssc;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completed</span><span class="params">(AsynchronousSocketChannel sc, Object attachment)</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.printf(<span class="string">&quot;[%s] %s connected\n&quot;</span>, Thread.currentThread().getName(), sc.getRemoteAddress());</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br><span class="line">            <span class="comment">// 读事件由 ReadHandler 处理</span></span><br><span class="line">            sc.read(buffer, buffer, <span class="keyword">new</span> <span class="title class_">ReadHandler</span>(sc));</span><br><span class="line">            <span class="comment">// 写事件由 WriteHandler 处理</span></span><br><span class="line">            sc.write(Charset.defaultCharset().encode(<span class="string">&quot;server hello!&quot;</span>), ByteBuffer.allocate(<span class="number">16</span>), <span class="keyword">new</span> <span class="title class_">WriteHandler</span>(sc));</span><br><span class="line">            <span class="comment">// 处理完第一个 accpet 时，需要再次调用 accept 方法来处理下一个 accept 事件</span></span><br><span class="line">            ssc.accept(<span class="literal">null</span>, <span class="built_in">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(Throwable exc, Object attachment)</span> &#123;</span><br><span class="line">            exc.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





</section>
    <!-- Tags START -->
    
      <div class="tags">
        <span>Tags:</span>
        
  <a href="/tags#NIO" >
    <span class="tag-code">NIO</span>
  </a>

      </div>
    
    <!-- Tags END -->
    <!-- NAV START -->
    
  <div class="nav-container">
    <!-- reverse left and right to put prev and next in a more logic postition -->
    
      <a class="nav-left" href="/2020/12/04/ElasticSearch%E4%B9%8B%E4%BA%AC%E4%B8%9C%E6%90%9C%E7%B4%A2%E5%AE%9E%E6%88%98/">
        <span class="nav-arrow">← </span>
        
          ElasticSearch之京东搜索实战
        
      </a>
    
    
      <a class="nav-right" href="/2021/05/26/Netty%E4%B8%93%E9%A2%98-Netty%E5%85%A5%E9%97%A8/">
        
          Netty专题-Netty入门
        
        <span class="nav-arrow"> →</span>
      </a>
    
  </div>

    <!-- NAV END -->
    <!-- 打赏 START -->
    
      <div class="money-like">
        <div class="reward-btn">
          赏
          <span class="money-code">
            <span class="alipay-code">
              <div class="code-image"></div>
              <b>使用支付宝打赏</b>
            </span>
            <span class="wechat-code">
              <div class="code-image"></div>
              <b>使用微信打赏</b>
            </span>
          </span>
        </div>
        <p class="notice">若你觉得我的文章对你有帮助，欢迎点击上方按钮对我打赏</p>
      </div>
    
    <!-- 打赏 END -->
    <!-- 二维码 START -->
    
      <div class="qrcode">
        <canvas id="share-qrcode"></canvas>
        <p class="notice">扫描二维码，分享此文章</p>
      </div>
    
    <!-- 二维码 END -->
    
      <!-- Utterances START -->
      <div id="utterances"></div>
      <script src="https://utteranc.es/client.js"
        repo="ltyeamin/blogtalks"
        issue-term="pathname"
        theme="github-light"
        crossorigin="anonymous"
        async></script>    
      <!-- Utterances END -->
    
  </article>
  <!-- Article END -->
  <!-- Catalog START -->
  
    <aside class="catalog-container">
  <div class="toc-main">
    <strong class="toc-title">Catalog</strong>
    
      <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#1-%E4%B8%89%E5%A4%A7%E7%BB%84%E4%BB%B6"><span class="toc-nav-text">1. 三大组件</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#1-1-Channel-amp-Buffer"><span class="toc-nav-text">1.1 Channel &amp; Buffer</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#1-2-Selector"><span class="toc-nav-text">1.2 Selector</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%89%88%E8%AE%BE%E8%AE%A1"><span class="toc-nav-text">多线程版设计</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-4"><a class="toc-nav-link" href="#%E2%9A%A0%EF%B8%8F-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%89%88%E7%BC%BA%E7%82%B9"><span class="toc-nav-text">⚠️ 多线程版缺点</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%89%88%E8%AE%BE%E8%AE%A1"><span class="toc-nav-text">线程池版设计</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-4"><a class="toc-nav-link" href="#%E2%9A%A0%EF%B8%8F-%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%89%88%E7%BC%BA%E7%82%B9"><span class="toc-nav-text">⚠️ 线程池版缺点</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#selector-%E7%89%88%E8%AE%BE%E8%AE%A1"><span class="toc-nav-text">selector 版设计</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#2-ByteBuffer"><span class="toc-nav-text">2. ByteBuffer</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-1-ByteBuffer-%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8%E5%A7%BF%E5%8A%BF"><span class="toc-nav-text">2.1  ByteBuffer 正确使用姿势</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-2-ByteBuffer-%E7%BB%93%E6%9E%84"><span class="toc-nav-text">2.2 ByteBuffer 结构</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%F0%9F%92%A1-%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7%E7%B1%BB"><span class="toc-nav-text">💡 调试工具类</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-3-ByteBuffer-%E5%B8%B8%E8%A7%81%E6%96%B9%E6%B3%95"><span class="toc-nav-text">2.3 ByteBuffer 常见方法</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%88%86%E9%85%8D%E7%A9%BA%E9%97%B4"><span class="toc-nav-text">分配空间</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%90%91-buffer-%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE"><span class="toc-nav-text">向 buffer 写入数据</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E4%BB%8E-buffer-%E8%AF%BB%E5%8F%96%E6%95%B0%E6%8D%AE"><span class="toc-nav-text">从 buffer 读取数据</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#mark-%E5%92%8C-reset"><span class="toc-nav-text">mark 和 reset</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%8E-ByteBuffer-%E4%BA%92%E8%BD%AC"><span class="toc-nav-text">字符串与 ByteBuffer 互转</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-5"><a class="toc-nav-link" href="#%E2%9A%A0%EF%B8%8F-Buffer-%E7%9A%84%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8"><span class="toc-nav-text">⚠️ Buffer 的线程安全</span></a></li></ol></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-4-Scattering-Reads"><span class="toc-nav-text">2.4 Scattering Reads</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-5-Gathering-Writes"><span class="toc-nav-text">2.5 Gathering Writes</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-6-%E7%BB%83%E4%B9%A0"><span class="toc-nav-text">2.6 练习</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#3-%E6%96%87%E4%BB%B6%E7%BC%96%E7%A8%8B"><span class="toc-nav-text">3. 文件编程</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#3-1-FileChannel"><span class="toc-nav-text">3.1 FileChannel</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E2%9A%A0%EF%B8%8F-FileChannel-%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F"><span class="toc-nav-text">⚠️ FileChannel 工作模式</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E8%8E%B7%E5%8F%96"><span class="toc-nav-text">获取</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E8%AF%BB%E5%8F%96"><span class="toc-nav-text">读取</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%86%99%E5%85%A5"><span class="toc-nav-text">写入</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%85%B3%E9%97%AD"><span class="toc-nav-text">关闭</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E4%BD%8D%E7%BD%AE"><span class="toc-nav-text">位置</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%A4%A7%E5%B0%8F"><span class="toc-nav-text">大小</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%BC%BA%E5%88%B6%E5%86%99%E5%85%A5"><span class="toc-nav-text">强制写入</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#3-2-%E4%B8%A4%E4%B8%AA-Channel-%E4%BC%A0%E8%BE%93%E6%95%B0%E6%8D%AE"><span class="toc-nav-text">3.2 两个 Channel 传输数据</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#3-3-Path"><span class="toc-nav-text">3.3 Path</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#3-4-Files"><span class="toc-nav-text">3.4 Files</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E2%9A%A0%EF%B8%8F-%E5%88%A0%E9%99%A4%E5%BE%88%E5%8D%B1%E9%99%A9"><span class="toc-nav-text">⚠️ 删除很危险</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#4-%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B"><span class="toc-nav-text">4. 网络编程</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#4-1-%E9%9D%9E%E9%98%BB%E5%A1%9E-vs-%E9%98%BB%E5%A1%9E"><span class="toc-nav-text">4.1 非阻塞 vs 阻塞</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E9%98%BB%E5%A1%9E"><span class="toc-nav-text">阻塞</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E9%9D%9E%E9%98%BB%E5%A1%9E"><span class="toc-nav-text">非阻塞</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8"><span class="toc-nav-text">多路复用</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#4-2-Selector"><span class="toc-nav-text">4.2 Selector</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%88%9B%E5%BB%BA"><span class="toc-nav-text">创建</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E7%BB%91%E5%AE%9A-Channel-%E4%BA%8B%E4%BB%B6"><span class="toc-nav-text">绑定 Channel 事件</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E7%9B%91%E5%90%AC-Channel-%E4%BA%8B%E4%BB%B6"><span class="toc-nav-text">监听 Channel 事件</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-4"><a class="toc-nav-link" href="#%F0%9F%92%A1-select-%E4%BD%95%E6%97%B6%E4%B8%8D%E9%98%BB%E5%A1%9E"><span class="toc-nav-text">💡 select 何时不阻塞</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#4-3-%E5%A4%84%E7%90%86-accept-%E4%BA%8B%E4%BB%B6"><span class="toc-nav-text">4.3 处理 accept 事件</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%F0%9F%92%A1-%E4%BA%8B%E4%BB%B6%E5%8F%91%E7%94%9F%E5%90%8E%E8%83%BD%E5%90%A6%E4%B8%8D%E5%A4%84%E7%90%86"><span class="toc-nav-text">💡 事件发生后能否不处理</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#4-4-%E5%A4%84%E7%90%86-read-%E4%BA%8B%E4%BB%B6"><span class="toc-nav-text">4.4 处理 read 事件</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%F0%9F%92%A1-%E4%B8%BA%E4%BD%95%E8%A6%81-iter-remove"><span class="toc-nav-text">💡 为何要 iter.remove()</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%F0%9F%92%A1-cancel-%E7%9A%84%E4%BD%9C%E7%94%A8"><span class="toc-nav-text">💡 cancel 的作用</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E2%9A%A0%EF%B8%8F-%E4%B8%8D%E5%A4%84%E7%90%86%E8%BE%B9%E7%95%8C%E7%9A%84%E9%97%AE%E9%A2%98"><span class="toc-nav-text">⚠️  不处理边界的问题</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E5%A4%84%E7%90%86%E6%B6%88%E6%81%AF%E7%9A%84%E8%BE%B9%E7%95%8C"><span class="toc-nav-text">处理消息的边界</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#ByteBuffer-%E5%A4%A7%E5%B0%8F%E5%88%86%E9%85%8D"><span class="toc-nav-text">ByteBuffer 大小分配</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#4-5-%E5%A4%84%E7%90%86-write-%E4%BA%8B%E4%BB%B6"><span class="toc-nav-text">4.5 处理 write 事件</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E4%B8%80%E6%AC%A1%E6%97%A0%E6%B3%95%E5%86%99%E5%AE%8C%E4%BE%8B%E5%AD%90"><span class="toc-nav-text">一次无法写完例子</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%F0%9F%92%A1-write-%E4%B8%BA%E4%BD%95%E8%A6%81%E5%8F%96%E6%B6%88"><span class="toc-nav-text">💡 write 为何要取消</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#4-6-%E6%9B%B4%E8%BF%9B%E4%B8%80%E6%AD%A5"><span class="toc-nav-text">4.6 更进一步</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%F0%9F%92%A1-%E5%88%A9%E7%94%A8%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%BC%98%E5%8C%96"><span class="toc-nav-text">💡 利用多线程优化</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%F0%9F%92%A1-%E5%A6%82%E4%BD%95%E6%8B%BF%E5%88%B0-cpu-%E4%B8%AA%E6%95%B0"><span class="toc-nav-text">💡 如何拿到 cpu 个数</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#4-7-UDP"><span class="toc-nav-text">4.7 UDP</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#5-NIO-vs-BIO"><span class="toc-nav-text">5. NIO vs BIO</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#5-1-stream-vs-channel"><span class="toc-nav-text">5.1 stream vs channel</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#5-2-IO-%E6%A8%A1%E5%9E%8B"><span class="toc-nav-text">5.2 IO 模型</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-4"><a class="toc-nav-link" href="#%F0%9F%94%96-%E5%8F%82%E8%80%83"><span class="toc-nav-text">🔖 参考</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#5-3-%E9%9B%B6%E6%8B%B7%E8%B4%9D"><span class="toc-nav-text">5.3 零拷贝</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E4%BC%A0%E7%BB%9F-IO-%E9%97%AE%E9%A2%98"><span class="toc-nav-text">传统 IO 问题</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-4"><a class="toc-nav-link" href="#NIO-%E4%BC%98%E5%8C%96"><span class="toc-nav-text">NIO 优化</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#5-3-AIO"><span class="toc-nav-text">5.3 AIO</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%96%87%E4%BB%B6-AIO"><span class="toc-nav-text">文件 AIO</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-4"><a class="toc-nav-link" href="#%F0%9F%92%A1-%E5%AE%88%E6%8A%A4%E7%BA%BF%E7%A8%8B"><span class="toc-nav-text">💡 守护线程</span></a></li></ol></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E7%BD%91%E7%BB%9C-AIO"><span class="toc-nav-text">网络 AIO</span></a></li></ol></li></ol></li></ol>
    
  </div>
</aside>
  
  <!-- Catalog END -->
</main>

<script>
  (function () {
    var url = 'http://example.com/2021/05/24/Netty专题-初识NIO/';
    var banner = ''
    if (banner !== '' && banner !== 'undefined' && banner !== 'null') {
      $('#article-banner').css({
        'background-image': 'url(' + banner + ')'
      })
    } else {
      $('#article-banner').geopattern(url)
    }
    $('.header').removeClass('fixed-header')

    // error image
    $(".markdown-content img").on('error', function() {
      $(this).attr('src', '/css/images/error_icon.png')
      $(this).css({
        'cursor': 'default'
      })
    })

    // zoom image
    $(".markdown-content img").on('click', function() {
      var src = $(this).attr('src')
      if (src !== '/css/images/error_icon.png') {
        var imageW = $(this).width()
        var imageH = $(this).height()

        var zoom = ($(window).width() * 0.95 / imageW).toFixed(2)
        zoom = zoom < 1 ? 1 : zoom
        zoom = zoom > 2 ? 2 : zoom
        var transY = (($(window).height() - imageH) / 2).toFixed(2)

        $('body').append('<div class="image-view-wrap"><div class="image-view-inner"><img src="'+ src +'" /></div></div>')
        $('.image-view-wrap').addClass('wrap-active')
        $('.image-view-wrap img').css({
          'width': `${imageW}`,
          'transform': `translate3d(0, ${transY}px, 0) scale3d(${zoom}, ${zoom}, 1)`
        })
        $('html').css('overflow', 'hidden')

        $('.image-view-wrap').on('click', function() {
          $(this).remove()
          $('html').attr('style', '')
        })
      }
    })
  })();
</script>


  <script>
    var qr = new QRious({
      element: document.getElementById('share-qrcode'),
      value: document.location.href
    });
  </script>






    <div class="scroll-top">
  <span class="arrow-icon"></span>
</div>
    <footer class="app-footer">
  <p class="copyright">
    &copy; 2024 | Proudly powered by <a href="https://hexo.io" target="_blank">Hexo</a>
    <br>
    Theme by <a target="_blank" rel="noopener" href="https://github.com/ltyeamin">tong.li</a>
  </p>
</footer>

<script>
  function async(u, c) {
    var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    o.src = u;
    if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
    s.parentNode.insertBefore(o, s);
  }
</script>
<script>
  async("https://cdn.staticfile.org/fastclick/1.0.6/fastclick.min.js", function(){
    FastClick.attach(document.body);
  })
</script>

<script>
  var hasLine = 'true';
  async("https://cdn.staticfile.org/highlight.js/9.12.0/highlight.min.js", function(){
    $('figure pre').each(function(i, block) {
      var figure = $(this).parents('figure');
      if (hasLine === 'false') {
        figure.find('.gutter').hide();
      }
      hljs.configure({useBR: true});
      var lang = figure.attr('class').split(' ')[1] || 'code';
      var codeHtml = $(this).html();
      var codeTag = document.createElement('code');
      codeTag.className = lang;
      codeTag.innerHTML = codeHtml;
      $(this).attr('class', '').empty().html(codeTag);
      figure.attr('data-lang', lang.toUpperCase());
      hljs.highlightBlock(block);
    });
  })
</script>
<!-- Baidu Tongji -->



<script src='https://cdn.staticfile.org/mermaid/8.11.2/mermaid.min.js'></script>



<script src="/js/script.js"></script>


  </body>
</html>